Skip to content
This repository was archived by the owner on Apr 14, 2022. It is now read-only.

Commit fee28c5

Browse files
author
Mikhail Arkhipov
authored
Update signature parameter handling to LSP 3.14 (#1111)
* Update to LSP 3.14+ * Undo change * Provide proper offsets * Add test * Undo debug * Add tests for old and new cases
1 parent c885338 commit fee28c5

11 files changed

Lines changed: 146 additions & 88 deletions

File tree

src/Analysis/Ast/Impl/Types/Definitions/IParameterInfo.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
// See the Apache Version 2.0 License for specific language governing
1414
// permissions and limitations under the License.
1515

16+
using Microsoft.Python.Core.Text;
17+
1618
namespace Microsoft.Python.Analysis.Types {
1719
/// <summary>
1820
/// Represents information about an individual parameter. Used for providing

src/Analysis/Ast/Impl/Types/ParameterInfo.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,13 @@
1414
// permissions and limitations under the License.
1515

1616
using System;
17+
using Microsoft.Python.Core.Text;
1718
using Microsoft.Python.Parsing.Ast;
1819

1920
namespace Microsoft.Python.Analysis.Types {
2021
internal sealed class ParameterInfo : IParameterInfo {
21-
public ParameterInfo(PythonAst ast, Parameter p, IPythonType type, IMember defaultValue, bool isGeneric) :
22-
this(p?.Name, type, p?.Kind, defaultValue) {
22+
public ParameterInfo(PythonAst ast, Parameter p, IPythonType type, IMember defaultValue, bool isGeneric)
23+
: this(p?.Name, type, p?.Kind, defaultValue) {
2324
Documentation = string.Empty;
2425
DefaultValueString = p?.DefaultValue?.ToCodeString(ast).Trim();
2526
if (DefaultValueString == "...") {

src/LanguageServer/Impl/Definitions/IDocumentationSource.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@
1414
// permissions and limitations under the License.
1515

1616
using Microsoft.Python.Analysis.Types;
17+
using Microsoft.Python.Core.Text;
1718
using Microsoft.Python.LanguageServer.Protocol;
1819

1920
namespace Microsoft.Python.LanguageServer {
2021
public interface IDocumentationSource {
2122
InsertTextFormat DocumentationFormat { get; }
2223
MarkupContent GetHover(string name, IMember member, IPythonType self = null);
23-
string GetSignatureString(IPythonFunctionType ft, IPythonType self, int overloadIndex = 0, string name = null);
24+
string GetSignatureString(IPythonFunctionType ft, IPythonType self, out IndexSpan[] parameterSpans, int overloadIndex = 0, string name = null);
2425
MarkupContent FormatParameterDocumentation(IParameterInfo parameter);
2526
MarkupContent FormatDocumentation(string documentation);
2627
}

src/LanguageServer/Impl/Implementation/Server.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,10 @@ public async Task<InitializeResult> InitializeAsync(InitializeParams @params, Ca
156156
ChooseDocumentationSource(_clientCaps?.textDocument?.hover?.contentFormat)
157157
);
158158

159+
var sigInfo = _clientCaps?.textDocument?.signatureHelp?.signatureInformation;
159160
_signatureSource = new SignatureSource(
160-
ChooseDocumentationSource(_clientCaps?.textDocument?.signatureHelp?.signatureInformation?.documentationFormat)
161+
ChooseDocumentationSource(sigInfo?.documentationFormat),
162+
sigInfo?.parameterInformation?.labelOffsetSupport == true
161163
);
162164

163165
return GetInitializeResult();

src/LanguageServer/Impl/Protocol/Classes.cs

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -278,13 +278,17 @@ public struct SignatureInformationCapabilities {
278278
/// property.The order describes the preferred format of the client.
279279
/// </summary>
280280
public string[] documentationFormat;
281-
281+
282282
/// <summary>
283-
/// When true, the label in the returned signature information will
284-
/// only contain the function name. Otherwise, the label will contain
285-
/// the full signature.
283+
/// Client capabilities specific to parameter information.
286284
/// </summary>
287-
public bool? _shortLabel;
285+
public struct ParameterInformationCapabilities {
286+
/// <summary>
287+
/// The client supports processing label offsets instead of a simple label string
288+
/// </summary>
289+
public bool? labelOffsetSupport;
290+
}
291+
public ParameterInformationCapabilities? parameterInformation;
288292
}
289293
public SignatureInformationCapabilities? signatureInformation;
290294
}
@@ -567,10 +571,6 @@ public sealed class Hover {
567571
/// The document version that range applies to.
568572
/// </summary>
569573
public int? _version;
570-
/// <summary>
571-
/// List of fully qualified type names for the expression
572-
/// </summary>
573-
public string[] _typeNames;
574574
}
575575

576576
[Serializable]
@@ -585,13 +585,21 @@ public sealed class SignatureInformation {
585585
public string label;
586586
public MarkupContent documentation;
587587
public ParameterInformation[] parameters;
588-
589-
public string[] _returnTypes;
590588
}
591589

592590
[Serializable]
593591
public sealed class ParameterInformation {
594-
public string label;
592+
/// <summary>
593+
/// The label of this signature.
594+
/// </summary>
595+
/// <remarks>
596+
/// LSP before 3.14: string label.
597+
/// LSP 3.14.0+: (int, int) range.
598+
/// Either a string or inclusive start and exclusive end offsets within its containing
599+
/// [signature label] (#SignatureInformation.label). *Note*: A label of type string must be
600+
/// a substring of its containing signature information's [label](#SignatureInformation.label).
601+
/// </remarks>
602+
public object label;
595603
public MarkupContent documentation;
596604
}
597605

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright(c) Microsoft Corporation
2+
// All rights reserved.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the License); you may not use
5+
// this file except in compliance with the License. You may obtain a copy of the
6+
// License at http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
9+
// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY
10+
// IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
11+
// MERCHANTABILITY OR NON-INFRINGEMENT.
12+
//
13+
// See the Apache Version 2.0 License for specific language governing
14+
// permissions and limitations under the License.
15+
16+
using System.Collections.Generic;
17+
using System.Linq;
18+
using Microsoft.Python.Analysis;
19+
using Microsoft.Python.Analysis.Types;
20+
using Microsoft.Python.Core.Text;
21+
22+
namespace Microsoft.Python.LanguageServer.Sources {
23+
internal abstract class DocumentationSource {
24+
public string GetSignatureString(IPythonFunctionType ft, IPythonType self, out IndexSpan[] parameterSpans, int overloadIndex = 0, string name = null) {
25+
var o = ft.Overloads[overloadIndex];
26+
27+
var parameterStrings = GetFunctionParameters(ft, out var parameterNameLengths);
28+
var returnDoc = o.GetReturnDocumentation(self);
29+
var annString = string.IsNullOrEmpty(returnDoc) ? string.Empty : $" -> {returnDoc}";
30+
31+
// Calculate parameter spans
32+
parameterSpans = new IndexSpan[parameterStrings.Length];
33+
name = name ?? ft.Name;
34+
var offset = name.Length + 1;
35+
for (var i = 0; i < parameterStrings.Length; i++) {
36+
parameterSpans[i] = IndexSpan.FromBounds(offset, offset + parameterNameLengths[i]);
37+
offset += parameterStrings[i].Length + 2; // name,<space>
38+
}
39+
40+
var combinedParameterString = string.Join(", ", parameterStrings);
41+
return $"{name}({combinedParameterString}){annString}";
42+
}
43+
44+
/// <summary>
45+
/// Constructs parameter strings that include parameter name, type and default value
46+
/// like 'x: int' or 'a = None'. Returns collection of parameter strings and
47+
/// the respective lengths of names for rendering in bold (as current parameter).
48+
/// </summary>
49+
private string[] GetFunctionParameters(IPythonFunctionType ft, out int[] parameterNameLengths, int overloadIndex = 0) {
50+
var o = ft.Overloads[overloadIndex]; // TODO: display all?
51+
var skip = ft.IsStatic || ft.IsUnbound ? 0 : 1;
52+
53+
var parameters = new string[o.Parameters.Count - skip];
54+
parameterNameLengths = new int[o.Parameters.Count - skip];
55+
for (var i = skip; i < o.Parameters.Count; i++) {
56+
string paramString;
57+
var p = o.Parameters[i];
58+
if (!string.IsNullOrEmpty(p.DefaultValueString)) {
59+
paramString = $"{p.Name}={p.DefaultValueString}";
60+
} else {
61+
paramString = p.Type.IsUnknown() ? p.Name : $"{p.Name}: {p.Type.Name}";
62+
}
63+
parameters[i - skip] = paramString;
64+
parameterNameLengths[i - skip] = p.Name.Length;
65+
}
66+
return parameters;
67+
}
68+
}
69+
}

src/LanguageServer/Impl/Sources/MarkdownDocumentationSource.cs

Lines changed: 4 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,14 @@
1313
// See the Apache Version 2.0 License for specific language governing
1414
// permissions and limitations under the License.
1515

16-
using System.Collections.Generic;
17-
using System.Linq;
1816
using Microsoft.Python.Analysis;
1917
using Microsoft.Python.Analysis.Types;
2018
using Microsoft.Python.Analysis.Values;
2119
using Microsoft.Python.LanguageServer.Documentation;
2220
using Microsoft.Python.LanguageServer.Protocol;
2321

2422
namespace Microsoft.Python.LanguageServer.Sources {
25-
internal sealed class MarkdownDocumentationSource : IDocumentationSource {
23+
internal sealed class MarkdownDocumentationSource : DocumentationSource, IDocumentationSource {
2624
public InsertTextFormat DocumentationFormat => InsertTextFormat.PlainText;
2725

2826
public MarkupContent GetHover(string name, IMember member, IPythonType self) {
@@ -69,20 +67,8 @@ public MarkupContent GetHover(string name, IMember member, IPythonType self) {
6967
};
7068
}
7169

72-
public MarkupContent FormatDocumentation(string documentation) {
73-
return new MarkupContent { kind = MarkupKind.Markdown, value = DocstringConverter.ToMarkdown(documentation) };
74-
}
75-
76-
public string GetSignatureString(IPythonFunctionType ft, IPythonType self, int overloadIndex = 0, string name = null) {
77-
var o = ft.Overloads[overloadIndex];
78-
79-
var parms = GetFunctionParameters(ft);
80-
var parmString = string.Join(", ", parms);
81-
var returnDoc = o.GetReturnDocumentation(self);
82-
var annString = string.IsNullOrEmpty(returnDoc) ? string.Empty : $" -> {returnDoc}";
83-
84-
return $"{name ?? ft.Name}({parmString}){annString}";
85-
}
70+
public MarkupContent FormatDocumentation(string documentation)
71+
=> new MarkupContent { kind = MarkupKind.Markdown, value = DocstringConverter.ToMarkdown(documentation) };
8672

8773
public MarkupContent FormatParameterDocumentation(IParameterInfo parameter) {
8874
if (!string.IsNullOrEmpty(parameter.Documentation)) {
@@ -100,21 +86,10 @@ private string GetPropertyHoverString(IPythonPropertyType prop, int overloadInde
10086
}
10187

10288
private string GetFunctionHoverString(IPythonFunctionType ft, IPythonType self, int overloadIndex = 0) {
103-
var sigString = GetSignatureString(ft, self, overloadIndex);
89+
var sigString = GetSignatureString(ft, self, out _, overloadIndex);
10490
var decTypeString = ft.DeclaringType != null ? $"{ft.DeclaringType.Name}." : string.Empty;
10591
var funcDoc = !string.IsNullOrEmpty(ft.Documentation) ? $"\n---\n{ft.MarkdownDoc()}" : string.Empty;
10692
return $"```\n{decTypeString}{sigString}\n```{funcDoc}";
10793
}
108-
109-
private IEnumerable<string> GetFunctionParameters(IPythonFunctionType ft, int overloadIndex = 0) {
110-
var o = ft.Overloads[overloadIndex]; // TODO: display all?
111-
var skip = ft.IsStatic || ft.IsUnbound ? 0 : 1;
112-
return o.Parameters.Skip(skip).Select(p => {
113-
if (!string.IsNullOrEmpty(p.DefaultValueString)) {
114-
return $"{p.Name}={p.DefaultValueString}";
115-
}
116-
return p.Type.IsUnknown() ? p.Name : $"{p.Name}: {p.Type.Name}";
117-
});
118-
}
11994
}
12095
}

src/LanguageServer/Impl/Sources/PlainTextDocumentationSource.cs

Lines changed: 4 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,14 @@
1313
// See the Apache Version 2.0 License for specific language governing
1414
// permissions and limitations under the License.
1515

16-
using System.Collections.Generic;
17-
using System.Linq;
1816
using Microsoft.Python.Analysis;
1917
using Microsoft.Python.Analysis.Types;
2018
using Microsoft.Python.Analysis.Values;
2119
using Microsoft.Python.LanguageServer.Documentation;
2220
using Microsoft.Python.LanguageServer.Protocol;
2321

2422
namespace Microsoft.Python.LanguageServer.Sources {
25-
internal sealed class PlainTextDocumentationSource : IDocumentationSource {
23+
internal sealed class PlainTextDocumentationSource : DocumentationSource, IDocumentationSource {
2624
public InsertTextFormat DocumentationFormat => InsertTextFormat.PlainText;
2725

2826
public MarkupContent GetHover(string name, IMember member, IPythonType self) {
@@ -69,20 +67,8 @@ public MarkupContent GetHover(string name, IMember member, IPythonType self) {
6967
};
7068
}
7169

72-
public MarkupContent FormatDocumentation(string documentation) {
73-
return new MarkupContent { kind = MarkupKind.PlainText, value = documentation };
74-
}
75-
76-
public string GetSignatureString(IPythonFunctionType ft, IPythonType self, int overloadIndex = 0, string name = null) {
77-
var o = ft.Overloads[overloadIndex];
78-
79-
var parms = GetFunctionParameters(ft);
80-
var parmString = string.Join(", ", parms);
81-
var returnDoc = o.GetReturnDocumentation(self);
82-
var annString = string.IsNullOrEmpty(returnDoc) ? string.Empty : $" -> {returnDoc}";
83-
84-
return $"{name ?? ft.Name}({parmString}){annString}";
85-
}
70+
public MarkupContent FormatDocumentation(string documentation)
71+
=> new MarkupContent { kind = MarkupKind.PlainText, value = documentation };
8672

8773
public MarkupContent FormatParameterDocumentation(IParameterInfo parameter) {
8874
if (!string.IsNullOrEmpty(parameter.Documentation)) {
@@ -100,21 +86,10 @@ private string GetPropertyHoverString(IPythonPropertyType prop, int overloadInde
10086
}
10187

10288
private string GetFunctionHoverString(IPythonFunctionType ft, IPythonType self, int overloadIndex = 0) {
103-
var sigString = GetSignatureString(ft, self, overloadIndex);
89+
var sigString = GetSignatureString(ft, self, out _, overloadIndex);
10490
var decTypeString = ft.DeclaringType != null ? $"{ft.DeclaringType.Name}." : string.Empty;
10591
var funcDoc = !string.IsNullOrEmpty(ft.Documentation) ? $"\n\n{ft.PlaintextDoc()}" : string.Empty;
10692
return $"{decTypeString}{sigString}{funcDoc}";
10793
}
108-
109-
private IEnumerable<string> GetFunctionParameters(IPythonFunctionType ft, int overloadIndex = 0) {
110-
var o = ft.Overloads[overloadIndex]; // TODO: display all?
111-
var skip = ft.IsStatic || ft.IsUnbound ? 0 : 1;
112-
return o.Parameters.Skip(skip).Select(p => {
113-
if (!string.IsNullOrEmpty(p.DefaultValueString)) {
114-
return $"{p.Name}={p.DefaultValueString}";
115-
}
116-
return p.Type.IsUnknown() ? p.Name : $"{p.Name}: {p.Type.Name}";
117-
});
118-
}
11994
}
12095
}

src/LanguageServer/Impl/Sources/SignatureSource.cs

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,12 @@
2727
namespace Microsoft.Python.LanguageServer.Sources {
2828
internal sealed class SignatureSource {
2929
private readonly IDocumentationSource _docSource;
30+
private readonly bool _labelOffsetSupport;
3031

31-
public SignatureSource(IDocumentationSource docSource) {
32+
public SignatureSource(IDocumentationSource docSource, bool labelOffsetSupport = true) {
3233
_docSource = docSource;
34+
// TODO: deprecate eventually.
35+
_labelOffsetSupport = labelOffsetSupport; // LSP 3.14.0+
3336
}
3437

3538
public SignatureHelp GetSignature(IDocumentAnalysis analysis, SourceLocation location) {
@@ -79,15 +82,24 @@ public SignatureHelp GetSignature(IDocumentAnalysis analysis, SourceLocation loc
7982
for (var i = 0; i < ft.Overloads.Count; i++) {
8083
var o = ft.Overloads[i];
8184

82-
var parameters = o.Parameters.Skip(skip).Select(p => new ParameterInformation {
83-
label = p.Name,
84-
documentation = _docSource.FormatParameterDocumentation(p)
85-
}).ToArray();
85+
var signatureLabel = _docSource.GetSignatureString(ft, selfType, out var parameterSpans, i, name);
86+
87+
var visibleParameterCount = o.Parameters.Count - skip;
88+
var parameterInfo = new ParameterInformation[visibleParameterCount];
89+
for (var j = 0; j < visibleParameterCount; j++) {
90+
var p = o.Parameters[j + skip];
91+
var ps = parameterSpans[j];
92+
93+
parameterInfo[j] = new ParameterInformation {
94+
label = _labelOffsetSupport ? new[] { ps.Start, ps.End } : (object)p.Name,
95+
documentation = _docSource.FormatParameterDocumentation(p)
96+
};
97+
}
8698

8799
signatures[i] = new SignatureInformation {
88-
label = _docSource.GetSignatureString(ft, selfType, i, name),
100+
label = signatureLabel,
89101
documentation = _docSource.FormatDocumentation(ft.Documentation),
90-
parameters = parameters
102+
parameters = parameterInfo
91103
};
92104
}
93105

src/LanguageServer/Test/FluentAssertions/SignatureInformationAssertions.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
// See the Apache Version 2.0 License for specific language governing
1414
// permissions and limitations under the License.
1515

16+
using System;
1617
using System.Collections.Generic;
1718
using System.Diagnostics.CodeAnalysis;
1819
using System.Linq;
@@ -42,14 +43,14 @@ public AndConstraint<SignatureInformationAssertions> HaveNoParameters(string bec
4243
return new AndConstraint<SignatureInformationAssertions>(this);
4344
}
4445

45-
public AndConstraint<SignatureInformationAssertions> OnlyHaveParameterLabels(params string[] labels)
46+
public AndConstraint<SignatureInformationAssertions> OnlyHaveParameterLabels(params int[][] labels)
4647
=> OnlyHaveParameterLabels(labels, string.Empty);
4748

48-
public AndConstraint<SignatureInformationAssertions> OnlyHaveParameterLabels(IEnumerable<string> labels, string because = "", params object[] reasonArgs) {
49+
public AndConstraint<SignatureInformationAssertions> OnlyHaveParameterLabels(IEnumerable<int[]> labelOffsets, string because = "", params object[] reasonArgs) {
4950
NotBeNull(because, reasonArgs);
5051

51-
var actual = Subject.parameters?.Select(i => i.label).ToArray() ?? new string[0];
52-
var expected = labels.ToArray();
52+
var actual = Subject.parameters?.Select(i => i.label).ToArray() ?? Array.Empty<int[]>();
53+
var expected = labelOffsets.ToArray();
5354

5455
var errorMessage = AssertionsUtilities.GetAssertCollectionOnlyContainsMessage(actual, expected, $"signature '{Subject.label}'", "parameter label", "parameter labels");
5556

0 commit comments

Comments
 (0)