diff --git a/Python/Product/Analysis/Analysis.csproj b/Python/Product/Analysis/Analysis.csproj index e345fa0103..dc011aba17 100644 --- a/Python/Product/Analysis/Analysis.csproj +++ b/Python/Product/Analysis/Analysis.csproj @@ -103,6 +103,7 @@ + diff --git a/Python/Product/Analysis/Interpreter/Ast/AstPythonModule.cs b/Python/Product/Analysis/Interpreter/Ast/AstPythonModule.cs index c65c7c54d3..72245c5df6 100644 --- a/Python/Product/Analysis/Interpreter/Ast/AstPythonModule.cs +++ b/Python/Product/Analysis/Interpreter/Ast/AstPythonModule.cs @@ -29,8 +29,9 @@ sealed class AstPythonModule : IPythonModule, IProjectEntry, ILocatedMember { private readonly IPythonInterpreter _interpreter; private readonly Dictionary _properties; private readonly List _childModules; - private bool _foundChildModules; private readonly Dictionary _members; + private bool _foundChildModules; + private string _documentation = string.Empty; public static IPythonModule FromFile( IPythonInterpreter interpreter, @@ -86,7 +87,6 @@ string moduleFullName internal AstPythonModule() { Name = string.Empty; - Documentation = string.Empty; FilePath = string.Empty; _properties = new Dictionary(); _childModules = new List(); @@ -96,7 +96,7 @@ internal AstPythonModule() { internal AstPythonModule(string moduleName, IPythonInterpreter interpreter, PythonAst ast, string filePath) { Name = moduleName; - Documentation = ast.Documentation; + _documentation = ast.Documentation; FilePath = filePath; DocumentUri = ProjectEntry.MakeDocumentUri(FilePath); Locations = new[] { new LocationInfo(filePath, DocumentUri, 1, 1) }; @@ -126,7 +126,19 @@ internal void AddChildModule(string name, IPythonModule module) { } public string Name { get; } - public string Documentation { get; } + public string Documentation { + get { + if(_documentation == null) { + _members.TryGetValue("__doc__", out var m); + _documentation = (m as AstPythonStringLiteral)?.Value ?? string.Empty; + if(string.IsNullOrEmpty(_documentation)) { + _members.TryGetValue($"_{Name}", out m); + _documentation = (m as AstNestedPythonModule)?.Documentation ?? string.Empty; + } + } + return _documentation; + } + } public string FilePath { get; } public Uri DocumentUri { get; } public PythonMemberType MemberType => PythonMemberType.Module; diff --git a/Python/Product/Analysis/Interpreter/Ast/AstScrapedPythonModule.cs b/Python/Product/Analysis/Interpreter/Ast/AstScrapedPythonModule.cs index cacc35ef4c..23658f8c9b 100644 --- a/Python/Product/Analysis/Interpreter/Ast/AstScrapedPythonModule.cs +++ b/Python/Product/Analysis/Interpreter/Ast/AstScrapedPythonModule.cs @@ -40,14 +40,21 @@ class AstScrapedPythonModule : IPythonModule public AstScrapedPythonModule(string name, string filePath) { Name = name ?? throw new ArgumentNullException(nameof(name)); - _documentation = string.Empty; _filePath = filePath; _members = new Dictionary(); } public string Name { get; } - public string Documentation => _documentation; + public string Documentation { + get { + if (_documentation == null) { + var m = GetMember(null, "__doc__") as AstPythonStringLiteral; + _documentation = m != null ? m.Value : string.Empty; + } + return _documentation; + } + } public PythonMemberType MemberType => PythonMemberType.Module; @@ -161,7 +168,7 @@ public void Imported(IModuleContext context) { proc.Start(); var exitCode = proc.Wait(60000); - + if (exitCode == null) { proc.Kill(); fact.Log(TraceLevel.Error, "ScrapeTimeout", proc.FileName, proc.Arguments); diff --git a/Python/Product/Analysis/LanguageServer/DisplayTextBuilder.cs b/Python/Product/Analysis/LanguageServer/DisplayTextBuilder.cs new file mode 100644 index 0000000000..4f63525d36 --- /dev/null +++ b/Python/Product/Analysis/LanguageServer/DisplayTextBuilder.cs @@ -0,0 +1,134 @@ +// Python Tools for Visual Studio +// Copyright(c) Microsoft Corporation +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the License); you may not use +// this file except in compliance with the License. You may obtain a copy of the +// License at http://www.apache.org/licenses/LICENSE-2.0 +// +// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS +// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY +// IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, +// MERCHANTABLITY OR NON-INFRINGEMENT. +// +// See the Apache Version 2.0 License for specific language governing +// permissions and limitations under the License. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using Microsoft.PythonTools.Analysis.Infrastructure; +using Microsoft.PythonTools.Interpreter; + +namespace Microsoft.PythonTools.Analysis.LanguageServer { + sealed class DisplayTextBuilder { + private readonly RestTextConverter _textConverter = new RestTextConverter(); + + public string MakeHoverText(IEnumerable values, string originalExpression, InformationDisplayOptions displayOptions) { + var result = new StringBuilder(); + var documentations = new HashSet(); + + foreach (var v in values) { + if(result.Length > 0) { + result.AppendLine(); + } + + var doc = GetDocString(v); + doc = displayOptions.trimDocumentationLines ? LimitLines(doc) : doc; + if (string.IsNullOrEmpty(doc)) { + continue; + } + + if (documentations.Add(doc)) { + result.AppendLine(doc); + } + } + + var displayText = result.ToString(); + var multiline = displayText.IndexOf('\n') >= 0; + if (displayOptions.trimDocumentationText && displayText.Length > displayOptions.maxDocumentationTextLength) { + displayText = displayText.Substring(0, + Math.Max(3, displayOptions.maxDocumentationTextLength) - 3) + "..."; + + result.Clear(); + result.Append(displayText); + } + + if (!string.IsNullOrEmpty(originalExpression)) { + if (displayOptions.trimDocumentationText && originalExpression.Length > displayOptions.maxDocumentationTextLength) { + originalExpression = originalExpression.Substring(0, + Math.Max(3, displayOptions.maxDocumentationTextLength) - 3) + "..."; + } + if (multiline) { + result.Insert(0, $"{originalExpression}:{Environment.NewLine}"); + } else if (result.Length > 0) { + result.Insert(0, $"{originalExpression}: "); + } else { + result.Append($"{originalExpression}: "); + } + } + + return _textConverter.ToMarkdown(result.ToString()); + } + + public string MakeModuleHoverText(ModuleReference modRef) { + // Return module information + var contents = "{0} : module".FormatUI(modRef.Name); + if (!string.IsNullOrEmpty(modRef.Module?.Documentation)) { + contents += $"{Environment.NewLine}{Environment.NewLine}{modRef.Module.Documentation}"; + } + return contents; + } + + private static string GetDocString(AnalysisValue v) { + var doc = !string.IsNullOrEmpty(v.Documentation) ? v.Documentation : string.Empty; + var desc = !string.IsNullOrEmpty(v.Description) ? v.Description : string.Empty; + if (v.MemberType == PythonMemberType.Instance || v.MemberType == PythonMemberType.Constant) { + return !string.IsNullOrEmpty(desc) ? desc : doc; + } + return doc.Length > desc.Length ? doc : desc; + } + + private static string LimitLines( + string str, + int maxLines = 30, + int charsPerLine = 200, + bool ellipsisAtEnd = true, + bool stopAtFirstBlankLine = false + ) { + if (string.IsNullOrEmpty(str)) { + return str; + } + + var lineCount = 0; + var prettyPrinted = new StringBuilder(); + var wasEmpty = true; + + using (var reader = new StringReader(str)) { + for (var line = reader.ReadLine(); line != null && lineCount < maxLines; line = reader.ReadLine()) { + if (string.IsNullOrWhiteSpace(line)) { + if (wasEmpty) { + continue; + } + wasEmpty = true; + if (stopAtFirstBlankLine) { + lineCount = maxLines; + break; + } + lineCount += 1; + prettyPrinted.AppendLine(); + } else { + wasEmpty = false; + lineCount += (line.Length / charsPerLine) + 1; + prettyPrinted.AppendLine(line); + } + } + } + if (ellipsisAtEnd && lineCount >= maxLines) { + prettyPrinted.AppendLine("..."); + } + return prettyPrinted.ToString().Trim(); + } + } +} diff --git a/Python/Product/Analysis/LanguageServer/RestTextConverter.cs b/Python/Product/Analysis/LanguageServer/RestTextConverter.cs index 2eac49adbb..c11867f85a 100644 --- a/Python/Product/Analysis/LanguageServer/RestTextConverter.cs +++ b/Python/Product/Analysis/LanguageServer/RestTextConverter.cs @@ -86,7 +86,7 @@ private string TransformLines(string docstring) { var sb = new StringBuilder(); foreach(var s in _md) { - sb.AppendLine(s + " "); // Keep hard line breaks + sb.AppendLine(s + " "); // Keep hard line breaks } return sb.ToString().Trim(); } diff --git a/Python/Product/Analysis/LanguageServer/Server.cs b/Python/Product/Analysis/LanguageServer/Server.cs index ee18e961e3..ae774c1def 100644 --- a/Python/Product/Analysis/LanguageServer/Server.cs +++ b/Python/Product/Analysis/LanguageServer/Server.cs @@ -34,11 +34,40 @@ namespace Microsoft.PythonTools.Analysis.LanguageServer { public sealed class Server : ServerBase, IDisposable { + /// + /// Implements ability to execute module reload on the analyzer thread + /// + private sealed class ReloadModulesQueueItem : IAnalyzable { + private readonly PythonAnalyzer _analyzer; + private TaskCompletionSource _tcs = new TaskCompletionSource(); + public Task Task => _tcs.Task; + + public ReloadModulesQueueItem(PythonAnalyzer analyzer) { + _analyzer = analyzer; + } + public void Analyze(CancellationToken cancel) { + if (cancel.IsCancellationRequested) { + return; + } + + var currentTcs = Interlocked.Exchange(ref _tcs, new TaskCompletionSource()); + var task = Task.Run(() => _analyzer.ReloadModulesAsync(), cancel); + try { + task.WaitAndUnwrapExceptions(); + currentTcs.TrySetResult(true); + } catch (OperationCanceledException oce) { + currentTcs.TrySetCanceled(oce.CancellationToken); + } catch (Exception ex) { + currentTcs.TrySetException(ex); + } + } + } + internal readonly AnalysisQueue _queue; internal readonly ParseQueue _parseQueue; private readonly Dictionary _pendingParse; private readonly VolatileCounter _pendingAnalysisEnqueue; - private readonly RestTextConverter _textConverter = new RestTextConverter(); + private readonly DisplayTextBuilder _displayTextBuilder = new DisplayTextBuilder(); // Uri does not consider #fragment for equality private readonly ConcurrentDictionary _projectFiles; @@ -47,13 +76,16 @@ public sealed class Server : ServerBase, IDisposable { // For pending changes, we use alternate comparer that checks #fragment private readonly ConcurrentDictionary> _pendingChanges; private readonly ManualResetEventSlim _documentChangeProcessingComplete = new ManualResetEventSlim(true); + private readonly TaskCompletionSource _analyzerCreationTcs = new TaskCompletionSource(); internal Task _loadingFromDirectory; internal PythonAnalyzer _analyzer; internal ClientCapabilities _clientCaps; + private InformationDisplayOptions _displayOptions; private bool _traceLogging; private bool _testEnvironment; + private ReloadModulesQueueItem _reloadModulesQueueItem; // If null, all files must be added manually private string _rootDir; @@ -67,6 +99,12 @@ public Server() { _projectFiles = new ConcurrentDictionary(); _pendingChanges = new ConcurrentDictionary>(UriEqualityComparer.IncludeFragment); _lastReportedDiagnostics = new ConcurrentDictionary>(); + _displayOptions = new InformationDisplayOptions { + trimDocumentationLines = true, + maxDocumentationLineLength = 200, + trimDocumentationText = true, + maxDocumentationTextLength = 4096 + }; } private void Analysis_UnhandledException(object sender, UnhandledExceptionEventArgs e) { @@ -87,13 +125,55 @@ private void TraceMessage(IFormattable message) { #region Client message handling public override async Task Initialize(InitializeParams @params) { - _testEnvironment = @params.initializationOptions.interpreter.properties.ContainsKey("TestEnvironment"); - if (_testEnvironment) { - // Test environment needs predictable initialization. - // Tests can only proceed when analysis is fully done. - _analyzer = CreateAnalyzer(@params.initializationOptions.interpreter).Result; + _testEnvironment = @params.initializationOptions.testEnvironment; + // Test environment needs predictable initialization. + if (@params.initializationOptions.asyncStartup && !_testEnvironment) { + CreateAnalyzer(@params.initializationOptions.interpreter).ContinueWith(t => { + if (t.IsFaulted) { + _analyzerCreationTcs.TrySetException(t.Exception); + } else { + try { + _analyzer = t.Result; + OnAnalyzerCreated(@params); + _analyzerCreationTcs.TrySetResult(true); + } catch (Exception ex) { + _analyzerCreationTcs.TrySetException(ex); + throw; + } + } + }).DoNotWait(); } else { - _analyzer = await CreateAnalyzer(@params.initializationOptions.interpreter); + try { + _analyzer = await CreateAnalyzer(@params.initializationOptions.interpreter); + OnAnalyzerCreated(@params); + _analyzerCreationTcs.TrySetResult(true); + } catch (Exception ex) { + _analyzerCreationTcs.TrySetException(ex); + throw; + } + } + + return new InitializeResult { + capabilities = new ServerCapabilities { + textDocumentSync = new TextDocumentSyncOptions { openClose = true, change = TextDocumentSyncKind.Incremental }, + completionProvider = new CompletionOptions { + triggerCharacters = new[] { "." }, + resolveProvider = true + }, + hoverProvider = true, + signatureHelpProvider = new SignatureHelpOptions { triggerCharacters = new[] { "(,)" } }, + // https://github.com/Microsoft/PTVS/issues/3803 + // definitionProvider = true, + referencesProvider = true + } + }; + } + + private void OnAnalyzerCreated(InitializeParams @params) { + _reloadModulesQueueItem = new ReloadModulesQueueItem(_analyzer); + + if (@params.initializationOptions.displayOptions != null) { + _displayOptions = @params.initializationOptions.displayOptions; } if (string.IsNullOrEmpty(_analyzer.InterpreterFactory?.Configuration?.InterpreterPath)) { @@ -119,21 +199,6 @@ public override async Task Initialize(InitializeParams @params LogMessage(MessageType.Log, $"Loading files from {_rootDir}"); _loadingFromDirectory = LoadFromDirectoryAsync(_rootDir); } - - return new InitializeResult { - capabilities = new ServerCapabilities { - textDocumentSync = new TextDocumentSyncOptions { openClose = true, change = TextDocumentSyncKind.Incremental }, - completionProvider = new CompletionOptions { - triggerCharacters = new[] { "." }, - resolveProvider = true - }, - hoverProvider = true, - signatureHelpProvider = new SignatureHelpOptions { triggerCharacters = new[] { "(,)" } }, - // https://github.com/Microsoft/PTVS/issues/3803 - // definitionProvider = true, - referencesProvider = true - } - }; } public override Task Shutdown() { @@ -144,6 +209,7 @@ public override Task Shutdown() { public override async Task DidOpenTextDocument(DidOpenTextDocumentParams @params) { TraceMessage($"Opening document {@params.textDocument.uri}"); + await _analyzerCreationTcs.Task; var entry = GetEntry(@params.textDocument.uri, throwIfMissing: false); var doc = entry as IDocument; @@ -168,6 +234,8 @@ public override async Task DidOpenTextDocument(DidOpenTextDocumentParams @params } public override void DidChangeTextDocument(DidChangeTextDocumentParams @params) { + _analyzerCreationTcs.Task.Wait(); + var changes = @params.contentChanges; if (changes == null) { return; @@ -236,6 +304,8 @@ public override void DidChangeTextDocument(DidChangeTextDocumentParams @params) } public override async Task DidChangeWatchedFiles(DidChangeWatchedFilesParams @params) { + await _analyzerCreationTcs.Task; + IProjectEntry entry; foreach (var c in @params.changes.MaybeEnumerate()) { switch (c.type) { @@ -262,7 +332,8 @@ public override async Task DidChangeWatchedFiles(DidChangeWatchedFilesParams @pa } } - public override Task DidCloseTextDocument(DidCloseTextDocumentParams @params) { + public override async Task DidCloseTextDocument(DidCloseTextDocumentParams @params) { + await _analyzerCreationTcs.Task; var doc = GetEntry(@params.textDocument.uri) as IDocument; if (doc != null) { @@ -272,16 +343,20 @@ public override Task DidCloseTextDocument(DidCloseTextDocumentParams @params) { // Pick up any changes on disk that we didn't know about EnqueueItem(doc, AnalysisPriority.Low); } - return Task.CompletedTask; } + public override async Task DidChangeConfiguration(DidChangeConfigurationParams @params) { + await _analyzerCreationTcs.Task; if (_analyzer == null) { LogMessage(MessageType.Error, "change configuration notification sent to uninitialized server"); return; } - await _analyzer.ReloadModulesAsync(); + // Make sure reload modules is executed on the analyzer thread. + var task = _reloadModulesQueueItem.Task; + _queue.Enqueue(_reloadModulesQueueItem, AnalysisPriority.Normal); + await task; // re-analyze all of the modules when we get a new set of modules loaded... foreach (var entry in _analyzer.ModulesByFilename) { @@ -289,7 +364,8 @@ public override async Task DidChangeConfiguration(DidChangeConfigurationParams @ } } - public override Task Completion(CompletionParams @params) { + public override async Task Completion(CompletionParams @params) { + await _analyzerCreationTcs.Task; IfTestWaitForAnalysisComplete(); // Make sure document is enqueued for processing _documentChangeProcessingComplete.Wait(200, CancellationToken); @@ -302,7 +378,7 @@ public override Task Completion(CompletionParams @params) { var analysis = entry?.Analysis; if (analysis == null) { TraceMessage($"No analysis found for {uri}"); - return Task.FromResult(new CompletionList { }); + return new CompletionList(); } var opts = GetMemberOptions.None; @@ -385,7 +461,7 @@ public override Task Completion(CompletionParams @params) { if (members == null) { TraceMessage($"No members found in document {uri}"); - return Task.FromResult(new CompletionList { }); + return new CompletionList(); } var filtered = members.Select(m => ToCompletionItem(m, opts)); @@ -397,7 +473,7 @@ public override Task Completion(CompletionParams @params) { var res = new CompletionList { items = filtered.ToArray() }; LogMessage(MessageType.Info, $"Found {res.items.Length} completions for {uri} at {@params.position} after filtering"); - return Task.FromResult(res); + return res; } public override Task CompletionItemResolve(CompletionItem item) { @@ -405,7 +481,8 @@ public override Task CompletionItemResolve(CompletionItem item) return Task.FromResult(item); } - public override Task SignatureHelp(TextDocumentPositionParams @params) { + public override async Task SignatureHelp(TextDocumentPositionParams @params) { + await _analyzerCreationTcs.Task; IfTestWaitForAnalysisComplete(); var uri = @params.textDocument.uri; @@ -416,7 +493,7 @@ public override Task SignatureHelp(TextDocumentPositionParams @pa var analysis = entry?.Analysis; if (analysis == null) { TraceMessage($"No analysis found for {uri}"); - return Task.FromResult(new SignatureHelp { }); + return new SignatureHelp(); } IEnumerable overloads; @@ -438,7 +515,7 @@ public override Task SignatureHelp(TextDocumentPositionParams @pa } } else { LogMessage(MessageType.Info, $"No signatures found in {uri} at {@params.position}"); - return Task.FromResult(new SignatureHelp { }); + return new SignatureHelp(); } } @@ -461,10 +538,12 @@ public override Task SignatureHelp(TextDocumentPositionParams @pa activeSignature = activeSignature, activeParameter = activeParameter }; - return Task.FromResult(sh); + return sh; } - public override Task FindReferences(ReferencesParams @params) { + public override async Task FindReferences(ReferencesParams @params) { + await _analyzerCreationTcs.Task; + var uri = @params.textDocument.uri; GetAnalysis(@params.textDocument, @params.position, @params._version, out var entry, out var tree); @@ -473,7 +552,7 @@ public override Task FindReferences(ReferencesParams @params) { var analysis = entry?.Analysis; if (analysis == null) { TraceMessage($"No analysis found for {uri}"); - return Task.FromResult(Array.Empty()); + return Array.Empty(); } int? version = null; @@ -524,7 +603,7 @@ public override Task FindReferences(ReferencesParams @params) { result = analysis.GetVariables(expr, @params.position); } else { LogMessage(MessageType.Info, $"No references found in {uri} at {@params.position}"); - return Task.FromResult(Array.Empty()); + return Array.Empty(); } } @@ -546,10 +625,11 @@ public override Task FindReferences(ReferencesParams @params) { .GroupBy(r => r, ReferenceComparer.Instance) .Select(g => g.OrderByDescending(r => (SourceLocation)r.range.end).ThenBy(r => (int?)r._kind ?? int.MaxValue).First()) .ToArray(); - return Task.FromResult(res); + return res; } - public override Task Hover(TextDocumentPositionParams @params) { + public override async Task Hover(TextDocumentPositionParams @params) { + await _analyzerCreationTcs.Task; IfTestWaitForAnalysisComplete(); var uri = @params.textDocument.uri; @@ -560,7 +640,7 @@ public override Task Hover(TextDocumentPositionParams @params) { var analysis = entry?.Analysis; if (analysis == null) { TraceMessage($"No analysis found for {uri}"); - return Task.FromResult(default(Hover)); + return default(Hover); } tree = GetParseTree(entry, uri, _clientCaps?.python?.completionsTimeout ?? Timeout.Infinite, out var version) ?? tree; @@ -568,12 +648,12 @@ public override Task Hover(TextDocumentPositionParams @params) { var index = tree.LocationToIndex(@params.position); var w = new ImportedModuleNameWalker(entry.ModuleName, index); tree.Walk(w); - ModuleReference modRef; if (!string.IsNullOrEmpty(w.ImportedName) && - _analyzer.Modules.TryImport(w.ImportedName, out modRef)) { - - // Return module information - return Task.FromResult(new Hover { contents = "{0} : module".FormatUI(w.ImportedName) }); + _analyzer.Modules.TryImport(w.ImportedName, out var modRef)) { + var contents = _displayTextBuilder.MakeModuleHoverText(modRef); + if (contents != null) { + return new Hover { contents = contents }; + } } Expression expr; @@ -594,7 +674,7 @@ public override Task Hover(TextDocumentPositionParams @params) { } if (expr == null) { LogMessage(MessageType.Info, $"No hover info found in {uri} at {@params.position}"); - return Task.FromResult(default(Hover)); + return default(Hover); } TraceMessage($"Getting hover for {expr.ToCodeString(tree, CodeFormattingOptions.Traditional)}"); @@ -613,15 +693,19 @@ public override Task Hover(TextDocumentPositionParams @params) { var names = values.Select(GetFullTypeName).Where(n => !string.IsNullOrEmpty(n)).Distinct().ToArray(); var res = new Hover { - contents = MakeHoverText(values, originalExpr), + contents = new MarkupContent { + kind = MarkupKind.Markdown, + value = _displayTextBuilder.MakeHoverText(values, originalExpr, _displayOptions) + }, range = exprSpan, _version = version, _typeNames = names }; - return Task.FromResult(res); + return res; } - public override Task WorkspaceSymbols(WorkspaceSymbolParams @params) { + public override async Task WorkspaceSymbols(WorkspaceSymbolParams @params) { + await _analyzerCreationTcs.Task; var members = Enumerable.Empty(); var opts = GetMemberOptions.ExcludeBuiltins | GetMemberOptions.DeclaredOnly; @@ -632,7 +716,7 @@ public override Task WorkspaceSymbols(WorkspaceSymbolParams } members = members.GroupBy(mr => mr.Name).Select(g => g.First()); - return Task.FromResult(members.Select(m => ToSymbolInformation(m)).ToArray()); + return members.Select(m => ToSymbolInformation(m)).ToArray(); } #endregion @@ -1301,11 +1385,11 @@ private MarkupKind SelectBestMarkup(IEnumerable requested, params Ma private string FormatParameter(ParameterResult p) { var res = new StringBuilder(p.Name); if (!string.IsNullOrEmpty(p.Type)) { - res.Append(" : "); + res.Append(": "); res.Append(p.Type); } if (!string.IsNullOrEmpty(p.DefaultValue)) { - res.Append(" = "); + res.Append('='); res.Append(p.DefaultValue); } return res.ToString(); diff --git a/Python/Product/Analysis/LanguageServer/Structures.cs b/Python/Product/Analysis/LanguageServer/Structures.cs index d3ecfdc604..72f9ea1458 100644 --- a/Python/Product/Analysis/LanguageServer/Structures.cs +++ b/Python/Product/Analysis/LanguageServer/Structures.cs @@ -206,6 +206,12 @@ public class MarkupContent { public static implicit operator MarkupContent(string text) => new MarkupContent { kind = MarkupKind.PlainText, value = text }; } + public class InformationDisplayOptions { + public bool trimDocumentationLines; + public int maxDocumentationLineLength; + public bool trimDocumentationText; + public int maxDocumentationTextLength; + } /// /// Required layout for the initializationOptions member of initializeParams @@ -229,6 +235,15 @@ public struct Interpreter { } public Interpreter interpreter; public string[] searchPaths; + public bool testEnvironment; + /// + /// Controls tooltip display appearance. Different between VS and VS Code. + /// + public InformationDisplayOptions displayOptions; + /// + /// If true, analyzer will be created asynchronously. Used in VS Code. + /// + public bool asyncStartup; } @@ -515,12 +530,12 @@ public struct ExecuteCommandOptions { } [Serializable] - public struct SaveOptions { + public class SaveOptions { public bool includeText; } [Serializable] - public struct TextDocumentSyncOptions { + public class TextDocumentSyncOptions { /// /// Open and close notifications are sent to the server. /// @@ -528,12 +543,12 @@ public struct TextDocumentSyncOptions { public TextDocumentSyncKind change; public bool willSave; public bool willSaveWaitUntil; - public SaveOptions? save; + public SaveOptions save; } [Serializable] public struct ServerCapabilities { - public TextDocumentSyncOptions? textDocumentSync; + public TextDocumentSyncOptions textDocumentSync; public bool hoverProvider; public CompletionOptions? completionProvider; public SignatureHelpOptions? signatureHelpProvider; diff --git a/Python/Product/Analysis/MemberResult.cs b/Python/Product/Analysis/MemberResult.cs index ffb9de49cb..e2655c0272 100644 --- a/Python/Product/Analysis/MemberResult.cs +++ b/Python/Product/Analysis/MemberResult.cs @@ -25,8 +25,6 @@ namespace Microsoft.PythonTools.Analysis { public struct MemberResult { - private readonly string _name; - private string _completion; private readonly Lazy> _vars; private readonly Lazy _type; @@ -35,23 +33,24 @@ public struct MemberResult { private static readonly Lazy> EmptyValues = new Lazy>(Enumerable.Empty); + #region Constructors internal MemberResult(string name, IEnumerable vars) { - _name = _completion = name; + Name = Completion = name; _vars = new Lazy>(() => vars.MaybeEnumerate()); _type = UnknownType; _type = new Lazy(GetMemberType); } public MemberResult(string name, PythonMemberType type) { - _name = _completion = name; + Name = Completion = name; _type = new Lazy(() => type); _vars = EmptyValues; } public MemberResult(string name, string completion, IEnumerable vars, PythonMemberType? type) { - _name = name; + Name = name; _vars = new Lazy>(() => vars.MaybeEnumerate()); - _completion = completion; + Completion = completion; _type = UnknownType; if (type != null) { _type = new Lazy(() => type.Value); @@ -61,120 +60,152 @@ public MemberResult(string name, string completion, IEnumerable v } internal MemberResult(string name, Func> vars, Func type) { - _name = _completion = name; + Name = Completion = name; _vars = vars == null ? EmptyValues : new Lazy>(vars); _type = type == null ? UnknownType : new Lazy(type); } + #endregion public MemberResult FilterCompletion(string completion) { return new MemberResult(Name, completion, Values, MemberType); } - public string Name { - get { return _name; } - } - - public string Completion { - get { return _completion; } - } + public string Name { get; } + public string Completion { get; } - private static string GetDescription(AnalysisValue ns) { - var d = ns?.ShortDescription; - if (string.IsNullOrEmpty(d)) { - return null; - } - switch (ns.MemberType) { - case PythonMemberType.Instance: - return "instance of " + d; - case PythonMemberType.Constant: - return "constant " + d; - } - return d; - } + /// + /// Gets the location(s) for the member(s) if they are available. + /// + /// New in 1.5. + /// + public IEnumerable Locations => Values.SelectMany(ns => ns.Locations); - private static IEnumerable SeparateMultipleMembers(IEnumerable values) { - foreach (var v in values) { - if (v is MultipleMemberInfo mm) { - foreach (var m in mm.Members) { - yield return m; - } - } else { - yield return v; - } - } - } + internal IEnumerable Values => _vars.Value; public string Documentation { get { var docs = new Dictionary>(); - var allTypes = new HashSet(); foreach (var ns in SeparateMultipleMembers(Values)) { - var docString = ns.Documentation?.TrimDocumentation(); + var docString = GetDocumentation(ns); var typeString = GetDescription(ns); - if (string.IsNullOrEmpty(docString)) { - docString = ""; + + // If first line of doc is already in the type string, then filter it out. + // This is because some functions have signature as a first doc line and + // some do not have one. We are already showing signature as part of the type. + var lines = docString.Split(new char[] { '\n' }).Where(x => x != "\r").ToArray(); + if (!string.IsNullOrEmpty(docString) && typeString != null && lines.Length > 1 && typeString.IndexOf(lines[0].Trim()) >= 0) { + docString = string.Join(Environment.NewLine, lines.Skip(1).ToArray()); } + if (!docs.TryGetValue(docString, out var docTypes)) { docs[docString] = docTypes = new HashSet(); } if (!string.IsNullOrEmpty(typeString)) { docTypes.Add(typeString); - allTypes.Add(typeString); } } var doc = new StringBuilder(); - - if (allTypes.Count == 0) { - return "unknown type"; - } else if (allTypes.Count == 1) { - doc.AppendLine(allTypes.First()); - doc.AppendLine(); - } else { - var types = allTypes.OrderBy(s => s).ToList(); - var orStr = types.Count == 2 ? " or " : ", or "; - doc.AppendLine(string.Join(", ", types.Take(types.Count - 1)) + orStr + types.Last()); - doc.AppendLine(); - } - - var typeToDoc = new Dictionary(); + var typeToDoc = new Dictionary>(); foreach (var docType in docs) { - if (string.IsNullOrEmpty(docType.Key)) { + if (!docType.Value.Any()) { continue; } - string typeDisplay = "unknown type"; + var typeDisplay = "unknown type"; var types = docType.Value.OrderBy(s => s).ToList(); - if (types.Count == 0) { - typeDisplay = ""; - } else if (types.Count == 1) { - if (allTypes.Count > 1) { - typeDisplay = types.First(); - } else { - typeDisplay = ""; - } + if (types.Count == 1) { + typeDisplay = types[0]; } else { var orStr = types.Count == 2 ? " or " : ", or "; typeDisplay = string.Join(", ", types.Take(types.Count - 1)) + orStr + types.Last(); } - typeToDoc[string.Join(",", types)] = typeDisplay + ": " + docType.Key; + typeToDoc[string.Join(",", types)] = new Tuple(typeDisplay, docType.Key); } foreach (var typeDoc in typeToDoc.OrderBy(kv => kv.Key)) { - doc.AppendLine(typeDoc.Value); + doc.Append(typeDoc.Value.Item1); + var details = typeDoc.Value.Item2; + if (!string.IsNullOrEmpty(details)) { + doc.AppendLine(":"); + doc.Append(details); + } + doc.AppendLine(); doc.AppendLine(); } - return Utils.CleanDocumentation(doc.ToString()); + return doc.ToString().Trim(); + } + } + + private static string GetDocumentation(AnalysisValue ns) { + var doc = ns.Documentation?.TrimDocumentation() ?? string.Empty; + if (ns.MemberType == PythonMemberType.Module) { + // Module doc does not nave example/signature lines like a function + // so just make it flow nicely in the tooltip by removing like breaks. + return doc.Replace('\n', ' ').Replace("\r", string.Empty); + } + // Documentation can contain something like + // func(a, b, c)\n\nThis function... + // i.e. with paragraph. We want to remove line breaks + // in the text after the paragraph breaks and tooltip UI + // to decide of the wrap and flow. + var ctr = 0; + var seenParagraphGap = false; + var result = new StringBuilder(doc.Length); + foreach (var c in doc) { + if (c == '\r') { + continue; + } + if (c == '\n') { + if (seenParagraphGap) { + result.Append(' '); + } else { + ctr++; + if (ctr < 3) { + result.AppendLine(); + seenParagraphGap = ctr == 2; + } + } + } else { + result.Append(c); + ctr = 0; + } + } + return result.ToString().Trim(); + } + private static string GetDescription(AnalysisValue ns) { + var d = ns?.ShortDescription; + if (string.IsNullOrEmpty(d)) { + return null; + } + switch (ns.MemberType) { + case PythonMemberType.Instance: + return "instance of " + d; + case PythonMemberType.Constant: + return "constant " + d; + } + return d; + } + + private static IEnumerable SeparateMultipleMembers(IEnumerable values) { + foreach (var v in values) { + if (v is MultipleMemberInfo mm) { + foreach (var m in mm.Members) { + yield return m; + } + } else { + yield return v; + } } } public PythonMemberType MemberType => _type.Value; private PythonMemberType GetMemberType() { - bool includesNone = false; - PythonMemberType result = PythonMemberType.Unknown; + var includesNone = false; + var result = PythonMemberType.Unknown; var allVars = Values.SelectMany(ns => { var mmi = ns as MultipleMemberInfo; @@ -225,33 +256,15 @@ private PythonMemberType GetMemberType() { return result; } - internal IEnumerable Values => _vars.Value; - - /// - /// Gets the location(s) for the member(s) if they are available. - /// - /// New in 1.5. - /// - public IEnumerable Locations => Values.SelectMany(ns => ns.Locations); - public override bool Equals(object obj) { if (!(obj is MemberResult)) { return false; } - return Name == ((MemberResult)obj).Name; } - public static bool operator ==(MemberResult x, MemberResult y) { - return x.Name == y.Name; - } - - public static bool operator !=(MemberResult x, MemberResult y) { - return x.Name != y.Name; - } - - public override int GetHashCode() { - return Name.GetHashCode(); - } + public static bool operator ==(MemberResult x, MemberResult y) => x.Name == y.Name; + public static bool operator !=(MemberResult x, MemberResult y) => x.Name != y.Name; + public override int GetHashCode() => Name.GetHashCode(); } } diff --git a/Python/Product/Analysis/ModuleReference.cs b/Python/Product/Analysis/ModuleReference.cs index 1d3061ec82..fc94e72c17 100644 --- a/Python/Product/Analysis/ModuleReference.cs +++ b/Python/Product/Analysis/ModuleReference.cs @@ -24,13 +24,14 @@ class ModuleReference { public IModule Module; private readonly Lazy> _references = new Lazy>(); + private string _name; public ModuleReference(IModule module = null, string name = null) { Module = module; - Name = name ?? ""; + _name = name; } - public string Name { get; } + public string Name => (_name ?? AnalysisModule?.Name) ?? string.Empty; public AnalysisValue AnalysisModule { get { diff --git a/Python/Product/Analysis/Values/BuiltinModule.cs b/Python/Product/Analysis/Values/BuiltinModule.cs index 6a4ce1c6a8..022c447e5d 100644 --- a/Python/Product/Analysis/Values/BuiltinModule.cs +++ b/Python/Product/Analysis/Values/BuiltinModule.cs @@ -14,6 +14,7 @@ // See the Apache Version 2.0 License for specific language governing // permissions and limitations under the License. +using System; using System.Collections.Generic; using System.Linq; using Microsoft.PythonTools.Analysis.Analyzer; @@ -50,7 +51,7 @@ public override IDictionary GetAllMembers(IModuleContext m if (_specializedValues != null) { foreach (var value in _specializedValues) { IAnalysisSet existing; - if(!res.TryGetValue(value.Key, out existing)) { + if (!res.TryGetValue(value.Key, out existing)) { res[value.Key] = value.Value; } else { var newSet = existing.Union(value.Value, canMutate: false); @@ -61,33 +62,12 @@ public override IDictionary GetAllMembers(IModuleContext m return res; } - public override string Documentation { - get { - return _type.Documentation; - } - } - - public override string Description { - get { - return "built-in module " + _interpreterModule.Name; - } - } - - public override string Name { - get { - return _interpreterModule.Name; - } - } - - public override IPythonType PythonType { - get { - return this.ProjectState.Types[BuiltinTypeId.Module]; - } - } + public override string Documentation => $"{Description}{Environment.NewLine}{_type.Documentation}"; + public override string Description => $"built-in module {_interpreterModule.Name}"; + public override string Name => _interpreterModule.Name; - public override PythonMemberType MemberType { - get { return _interpreterModule.MemberType; } - } + public override IPythonType PythonType => ProjectState.Types[BuiltinTypeId.Module]; + public override PythonMemberType MemberType => _interpreterModule.MemberType; internal override BuiltinTypeId TypeId => BuiltinTypeId.Module; diff --git a/Python/Product/Analysis/Values/FunctionInfo.cs b/Python/Product/Analysis/Values/FunctionInfo.cs index 8875a1d210..927d836c0f 100644 --- a/Python/Product/Analysis/Values/FunctionInfo.cs +++ b/Python/Product/Analysis/Values/FunctionInfo.cs @@ -204,11 +204,11 @@ internal IEnumerable> GetParameterString() { yield return new KeyValuePair(WellKnownRichDescriptionKinds.Parameter, name); if (!string.IsNullOrWhiteSpace(annotation)) { - yield return new KeyValuePair(WellKnownRichDescriptionKinds.Misc, " : "); + yield return new KeyValuePair(WellKnownRichDescriptionKinds.Misc, ": "); yield return new KeyValuePair(WellKnownRichDescriptionKinds.Type, annotation); } if (!string.IsNullOrWhiteSpace(defaultValue)) { - yield return new KeyValuePair(WellKnownRichDescriptionKinds.Misc, " = "); + yield return new KeyValuePair(WellKnownRichDescriptionKinds.Misc, "="); yield return new KeyValuePair(WellKnownRichDescriptionKinds.Misc, defaultValue); } } diff --git a/Python/Product/Analysis/Values/IModule.cs b/Python/Product/Analysis/Values/IModule.cs index ef607033c7..31f49385fd 100644 --- a/Python/Product/Analysis/Values/IModule.cs +++ b/Python/Product/Analysis/Values/IModule.cs @@ -30,5 +30,7 @@ interface IModule { IEnumerable GetModuleMemberNames(IModuleContext context); IAnalysisSet GetModuleMember(Node node, AnalysisUnit unit, string name, bool addRef = true, InterpreterScope linkedScope = null, string linkedName = null); void Imported(AnalysisUnit unit); + string Description { get; } + string Documentation { get; } } } diff --git a/Python/Product/Analysis/Values/Protocols.cs b/Python/Product/Analysis/Values/Protocols.cs index 49a325fa99..e00ba3405f 100644 --- a/Python/Product/Analysis/Values/Protocols.cs +++ b/Python/Product/Analysis/Values/Protocols.cs @@ -670,7 +670,7 @@ public override void SetMember(Node node, AnalysisUnit unit, string name, IAnaly public override IEnumerable> GetRichDescription() { yield return new KeyValuePair(WellKnownRichDescriptionKinds.Name, Name); - foreach (var kv in _values.Types.GetRichDescriptions(prefix: " : ", unionPrefix: "{", unionSuffix: "}")) { + foreach (var kv in _values.Types.GetRichDescriptions(prefix: ": ", unionPrefix: "{", unionSuffix: "}")) { yield return kv; } } diff --git a/Python/Product/Analysis/Values/Utils.cs b/Python/Product/Analysis/Values/Utils.cs index 7ce5207444..d07e700d44 100644 --- a/Python/Product/Analysis/Values/Utils.cs +++ b/Python/Product/Analysis/Values/Utils.cs @@ -46,25 +46,6 @@ internal static string StripDocumentation(string doc) { return result.ToString(); } - internal static string CleanDocumentation(string doc) { - int ctr = 0; - var result = new StringBuilder(doc.Length); - foreach (char c in doc) { - if (c == '\r') { - // pass - } else if (c == '\n') { - ctr++; - if (ctr < 3) { - result.Append("\r\n"); - } - } else { - result.Append(c); - ctr = 0; - } - } - return result.ToString().Trim(); - } - internal static IAnalysisSet GetReturnTypes(IPythonFunction func, PythonAnalyzer projectState) { return AnalysisSet.UnionAll(func.Overloads .Where(fn => fn.ReturnType != null) diff --git a/Python/Product/Analysis/scrape_module.py b/Python/Product/Analysis/scrape_module.py index 552117091a..b06a4f422b 100644 --- a/Python/Product/Analysis/scrape_module.py +++ b/Python/Product/Analysis/scrape_module.py @@ -383,10 +383,8 @@ def _init_restype_fromknown(self, scope_alias): return "return " + restype def _init_argspec_fromdocstring(self, defaults, doc=None, override_name=None): - allow_name_mismatch = True if not doc: doc = getattr(self.callable, '__doc__', None) - allow_name_mismatch = False if not isinstance(doc, str): return @@ -394,6 +392,11 @@ def _init_argspec_fromdocstring(self, defaults, doc=None, override_name=None): if not doc: return + if(override_name): + allow_name_mismatch = override_name not in doc + else: + allow_name_mismatch = False + return self._parse_funcdef(doc, allow_name_mismatch, defaults, override_name) def _make_unique_name(self, name, seen_names): @@ -691,6 +694,8 @@ def _lines_with_members(self): yield ' ' + repr(self.documentation) if self.members: for mi in self.members: + if hasattr(mi, 'documentation') and mi.documentation != None and not isinstance(mi.documentation, str): + continue if mi is not MemberInfo.NO_VALUE: yield mi.as_str(' ') else: @@ -889,7 +894,7 @@ def _collect_members(self, mod, members, substitutes, outer_member): else: if not self._should_add_value(value): continue - if self._mro_contains(mro, name, value): + if name != '__init__' and self._mro_contains(mro, name, value): continue members.append(MemberInfo(name, value, scope=scope, module=self.module_name, module_doc=mod_doc, scope_alias=scope_alias)) diff --git a/Python/Product/VSCode/AnalysisVsc/LanguageServer.cs b/Python/Product/VSCode/AnalysisVsc/LanguageServer.cs index 7800fdb291..2c655affdb 100644 --- a/Python/Product/VSCode/AnalysisVsc/LanguageServer.cs +++ b/Python/Product/VSCode/AnalysisVsc/LanguageServer.cs @@ -17,11 +17,13 @@ using System; using System.Diagnostics; using System.Linq; +using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.DsTools.Core.Disposables; using Microsoft.DsTools.Core.Services; using Microsoft.DsTools.Core.Services.Shell; +using Microsoft.PythonTools.Analysis; using Microsoft.PythonTools.Analysis.Infrastructure; using Microsoft.PythonTools.Analysis.LanguageServer; using Microsoft.PythonTools.VsCode.Core.Shell; @@ -39,6 +41,7 @@ public sealed class LanguageServer : IDisposable { private readonly DisposableBag _disposables = new DisposableBag(nameof(LanguageServer)); private readonly Server _server = new Server(); private readonly CancellationTokenSource _sessionTokenSource = new CancellationTokenSource(); + private readonly RestTextConverter _textConverter = new RestTextConverter(); private IUIService _ui; private ITelemetryService _telemetry; private JsonRpc _vscode; @@ -137,9 +140,9 @@ public async Task Exit() { private void MonitorParentProcess(Process process) { Task.Run(async () => { - while(!_sessionTokenSource.IsCancellationRequested) { + while (!_sessionTokenSource.IsCancellationRequested) { await Task.Delay(2000); - if(process.HasExited) { + if (process.HasExited) { _sessionTokenSource.Cancel(); } } @@ -212,7 +215,7 @@ public Task Hover(JToken token) [JsonRpcMethod("textDocument/signatureHelp")] public Task SignatureHelp(JToken token) - => _server.SignatureHelp(token.ToObject()); + => _server.SignatureHelp(token.ToObject()); [JsonRpcMethod("textDocument/definition")] public Task GotoDefinition(JToken token) diff --git a/Python/Product/VSCode/AnalysisVsc/Program.cs b/Python/Product/VSCode/AnalysisVsc/Program.cs index 1b82e8d3a7..fb8d7a5231 100644 --- a/Python/Product/VSCode/AnalysisVsc/Program.cs +++ b/Python/Product/VSCode/AnalysisVsc/Program.cs @@ -42,8 +42,12 @@ public static void Main(string[] args) { private static void CheckDebugMode() { #if WAIT_FOR_DEBUGGER + var start = DateTime.Now; while (!System.Diagnostics.Debugger.IsAttached) { System.Threading.Thread.Sleep(1000); + if ((DateTime.Now - start).TotalMilliseconds > 15000) { + break; + } } #endif } diff --git a/Python/Tests/Analysis/AnalysisSaveTest.cs b/Python/Tests/Analysis/AnalysisSaveTest.cs index 1770a8abd4..d9f3428c42 100644 --- a/Python/Tests/Analysis/AnalysisSaveTest.cs +++ b/Python/Tests/Analysis/AnalysisSaveTest.cs @@ -175,7 +175,7 @@ import test var allMembers = newMod.Analysis.GetAllAvailableMembersByIndex(pos, GetMemberOptions.None); Assert.AreEqual( - "class test.Aliased or function Aliased(fob)\r\n\r\nclass test.Aliased: class doc\r\n\r\nfunction Aliased(fob): function doc", + "class test.Aliased:\r\nclass doc\r\n\r\nfunction Aliased(fob):\r\nfunction doc", allMembers.First(x => x.Name == "Aliased").Documentation ); newPs.Analyzer.AssertHasParameters("FunctionNoRetType", "value"); diff --git a/Python/Tests/Analysis/AnalysisTest.cs b/Python/Tests/Analysis/AnalysisTest.cs index 5b1775261e..02a2717e09 100644 --- a/Python/Tests/Analysis/AnalysisTest.cs +++ b/Python/Tests/Analysis/AnalysisTest.cs @@ -5832,7 +5832,7 @@ def with_params_default_starargs(*args, **kwargs): entry.AssertIsInstance("d", "fob"); entry.AssertDescription("sys", "built-in module sys"); entry.AssertDescription("f", "test-module.f() -> str"); - entry.AssertDescription("fob.f", "test-module.fob.f(self : fob)\r\ndeclared in fob"); + entry.AssertDescription("fob.f", "test-module.fob.f(self: fob)\r\ndeclared in fob"); entry.AssertDescription("fob().g", "method g of test-module.fob objects "); entry.AssertDescription("fob", "class test-module.fob(object)"); //AssertUtil.ContainsExactly(entry.GetVariableDescriptionsByIndex("System.StringSplitOptions.RemoveEmptyEntries", 1), "field of type StringSplitOptions"); @@ -5844,13 +5844,13 @@ def with_params_default_starargs(*args, **kwargs): entry.AssertDescription("docstr_func", "test-module.docstr_func() -> int\r\nuseful documentation"); entry.AssertDescription("with_params", "test-module.with_params(a, b, c)"); - entry.AssertDescription("with_params_default", "test-module.with_params_default(a, b, c : int = 100)"); - entry.AssertDescription("with_params_default_2", "test-module.with_params_default_2(a, b, c : list = [])"); - entry.AssertDescription("with_params_default_3", "test-module.with_params_default_3(a, b, c : tuple = ())"); - entry.AssertDescription("with_params_default_4", "test-module.with_params_default_4(a, b, c : dict = {})"); - entry.AssertDescription("with_params_default_2a", "test-module.with_params_default_2a(a, b, c : list = [...])"); - entry.AssertDescription("with_params_default_3a", "test-module.with_params_default_3a(a, b, c : tuple = (...))"); - entry.AssertDescription("with_params_default_4a", "test-module.with_params_default_4a(a, b, c : dict = {...})"); + entry.AssertDescription("with_params_default", "test-module.with_params_default(a, b, c: int=100)"); + entry.AssertDescription("with_params_default_2", "test-module.with_params_default_2(a, b, c: list=[])"); + entry.AssertDescription("with_params_default_3", "test-module.with_params_default_3(a, b, c: tuple=())"); + entry.AssertDescription("with_params_default_4", "test-module.with_params_default_4(a, b, c: dict={})"); + entry.AssertDescription("with_params_default_2a", "test-module.with_params_default_2a(a, b, c: list=[...])"); + entry.AssertDescription("with_params_default_3a", "test-module.with_params_default_3a(a, b, c: tuple=(...))"); + entry.AssertDescription("with_params_default_4a", "test-module.with_params_default_4a(a, b, c: dict={...})"); entry.AssertDescription("with_params_default_starargs", "test-module.with_params_default_starargs(*args, **kwargs)"); // method which returns itself, we shouldn't stack overflow producing the help... @@ -6601,7 +6601,7 @@ def fn(self): var entry = ProcessText(code); Assert.AreEqual( - "test-module.A.fn(self : A) -> lambda: 123 -> int\ndeclared in A", + "test-module.A.fn(self: A) -> lambda: 123 -> int\ndeclared in A", entry.GetDescriptions("A.fn", 0).Single().Replace("\r\n", "\n") ); } diff --git a/Python/Tests/Analysis/LanguageServerTests.cs b/Python/Tests/Analysis/LanguageServerTests.cs index f96dddce7b..9ad3fa514c 100644 --- a/Python/Tests/Analysis/LanguageServerTests.cs +++ b/Python/Tests/Analysis/LanguageServerTests.cs @@ -85,7 +85,9 @@ await s.Initialize(new InitializeParams { assembly = typeof(AstPythonInterpreterFactory).Assembly.Location, typeName = typeof(AstPythonInterpreterFactory).FullName, properties = properties - } + }, + asyncStartup = false, + testEnvironment = true }, capabilities = new ClientCapabilities { python = new PythonClientCapabilities { @@ -342,7 +344,7 @@ def f(a, *b, **c): pass "); await AssertSignature(s, mod, new SourceLocation(1, 3), - new string[] { "f()", "f(a)", "f(a, b)", "f(a, *b : tuple)", "f(a, **b : dict)", "f(a, *b : tuple, **c : dict)" }, + new string[] { "f()", "f(a)", "f(a, b)", "f(a, *b: tuple)", "f(a, **b: dict)", "f(a, *b: tuple, **c: dict)" }, new string[0] ); @@ -361,7 +363,7 @@ def f(a = 2, b): pass "); await AssertSignature(s, mod, new SourceLocation(1, 3), - new string[] { "f(a : int)", "f(a : int = 2, b : int)", "f(x : str, y : str)" }, + new string[] { "f(a: int)", "f(a: int=2, b: int)", "f(x: str, y: str)" }, new string[0] ); } @@ -500,7 +502,7 @@ def g(self): await AssertHover(s, mod, new SourceLocation(13, 1), "c: C", new[] { "test-module.C" }, new SourceSpan(13, 1, 13, 2)); await AssertHover(s, mod, new SourceLocation(14, 7), "c: C", new[] { "test-module.C" }, new SourceSpan(14, 7, 14, 8)); await AssertHover(s, mod, new SourceLocation(14, 9), "c.f: method f of test-module.C objects*", new[] { "test-module.C.f" }, new SourceSpan(14, 7, 14, 10)); - await AssertHover(s, mod, new SourceLocation(14, 1), "c_g: test-module.C.f.g(self)\r\ndeclared in C.f", new[] { "test-module.C.f.g" }, new SourceSpan(14, 1, 14, 4)); + await AssertHover(s, mod, new SourceLocation(14, 1), $"c_g: test-module.C.f.g(self) {Environment.NewLine}declared in C.f", new[] { "test-module.C.f.g" }, new SourceSpan(14, 1, 14, 4)); await AssertHover(s, mod, new SourceLocation(16, 1), "x: int, float", new[] { "int", "float" }, new SourceSpan(16, 1, 16, 2)); } diff --git a/Python/Tests/Analysis/TypeAnnotationTests.cs b/Python/Tests/Analysis/TypeAnnotationTests.cs index 2dc6d8feb4..44cdb8ccfa 100644 --- a/Python/Tests/Analysis/TypeAnnotationTests.cs +++ b/Python/Tests/Analysis/TypeAnnotationTests.cs @@ -249,8 +249,8 @@ public void TypingModuleNamedTupleAnalysis() { analyzer.WaitForAnalysis(); analyzer.AssertDescription("n", "tuple"); - analyzer.AssertDescription("n1", "n1(x : int, y : float)"); - analyzer.AssertDescription("n2", "n2(x : int, y : float)"); + analyzer.AssertDescription("n1", "n1(x: int, y: float)"); + analyzer.AssertDescription("n2", "n2(x: int, y: float)"); analyzer.AssertIsInstance("n1_x", BuiltinTypeId.Int); analyzer.AssertIsInstance("n1_y", BuiltinTypeId.Float); @@ -294,7 +294,7 @@ public void TypingModuleNamedTypeAlias() { analyzer.AssertIsInstance("i", BuiltinTypeId.Int); analyzer.AssertIsInstance("sl", BuiltinTypeId.List); analyzer.AssertIsInstance("sl_0", BuiltinTypeId.Str); - analyzer.AssertDescription("n1", "MyNamedTuple(x : int)"); + analyzer.AssertDescription("n1", "MyNamedTuple(x: int)"); analyzer.AssertIsInstance("n1.x", BuiltinTypeId.Int); }