mirror of
https://github.com/dart-lang/sdk
synced 2024-09-15 23:09:48 +00:00
[analysis_server] Support LSP SignatureHelp for type argument lists
See https://github.com/Dart-Code/Dart-Code/issues/2834. Change-Id: Idce0d0b133b1f40c1953e5f1c5f4531701a68c56 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/204404 Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
parent
e1620d6a27
commit
ad6c732663
|
@ -28,7 +28,6 @@ class DartUnitSignatureComputer {
|
|||
|
||||
bool get offsetIsValid => _node != null;
|
||||
|
||||
// Return the closest argument list surrounding the [_node].
|
||||
/// Returns the computed signature information, maybe `null`.
|
||||
AnalysisGetSignatureResult? compute() {
|
||||
var argumentList = _findArgumentList();
|
||||
|
@ -80,6 +79,7 @@ class DartUnitSignatureComputer {
|
|||
defaultValue: param.defaultValueCode);
|
||||
}
|
||||
|
||||
/// Return the closest argument list surrounding the [_node].
|
||||
ArgumentList? _findArgumentList() {
|
||||
var node = _node;
|
||||
while (node != null && node is! ArgumentList) {
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import 'package:analysis_server/lsp_protocol/protocol_generated.dart' as lsp;
|
||||
import 'package:analysis_server/src/computer/computer_hover.dart';
|
||||
import 'package:analysis_server/src/lsp/dartdoc.dart';
|
||||
import 'package:analysis_server/src/lsp/mapping.dart';
|
||||
import 'package:analysis_server/src/utilities/extensions/ast.dart';
|
||||
import 'package:analyzer/dart/ast/ast.dart';
|
||||
import 'package:analyzer/dart/element/element.dart';
|
||||
import 'package:analyzer/src/dart/ast/element_locator.dart';
|
||||
import 'package:analyzer/src/dart/ast/utilities.dart';
|
||||
import 'package:analyzer/src/dartdoc/dartdoc_directive_info.dart';
|
||||
|
||||
/// A computer for the signature help information about the type parameters for
|
||||
/// the [TypeArgumentList] surrounding the specified offset of a Dart
|
||||
/// [CompilationUnit].
|
||||
class DartTypeArgumentsSignatureComputer {
|
||||
final DartdocDirectiveInfo _dartdocInfo;
|
||||
final AstNode? _node;
|
||||
final Set<lsp.MarkupKind>? preferredFormats;
|
||||
late TypeArgumentList _argumentList;
|
||||
final bool _isNonNullableByDefault;
|
||||
DartTypeArgumentsSignatureComputer(
|
||||
this._dartdocInfo,
|
||||
CompilationUnit _unit,
|
||||
int _offset,
|
||||
this.preferredFormats,
|
||||
) : _node = NodeLocator(_offset).searchWithin(_unit),
|
||||
_isNonNullableByDefault = _unit.isNonNullableByDefault;
|
||||
|
||||
/// The [TypeArgumentList] node located by [compute].
|
||||
TypeArgumentList get argumentList => _argumentList;
|
||||
|
||||
bool get offsetIsValid => _node != null;
|
||||
|
||||
/// Returns the computed signature information, maybe `null`.
|
||||
lsp.SignatureHelp? compute() {
|
||||
var argumentList = _findTypeArgumentList();
|
||||
if (argumentList == null) {
|
||||
return null;
|
||||
}
|
||||
final parent = argumentList.parent;
|
||||
Element? element;
|
||||
if (parent is TypeName) {
|
||||
element = ElementLocator.locate(parent.name);
|
||||
} else if (parent is MethodInvocation) {
|
||||
element = ElementLocator.locate(parent.methodName);
|
||||
}
|
||||
if (element is! TypeParameterizedElement ||
|
||||
element.typeParameters.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
_argumentList = argumentList;
|
||||
|
||||
final label =
|
||||
element.getDisplayString(withNullability: _isNonNullableByDefault);
|
||||
final documentation =
|
||||
DartUnitHoverComputer.computeDocumentation(_dartdocInfo, element)?.full;
|
||||
|
||||
return _toSignatureHelp(
|
||||
label,
|
||||
cleanDartdoc(documentation),
|
||||
element.typeParameters,
|
||||
);
|
||||
}
|
||||
|
||||
/// Return the closest type argument list surrounding the [_node].
|
||||
TypeArgumentList? _findTypeArgumentList() {
|
||||
var node = _node;
|
||||
while (node != null && node is! TypeArgumentList) {
|
||||
// Certain nodes don't make sense to search above for an argument list
|
||||
// (for example when inside a function expression).
|
||||
if (node is FunctionExpression) {
|
||||
return null;
|
||||
}
|
||||
node = node.parent;
|
||||
}
|
||||
return node as TypeArgumentList?;
|
||||
}
|
||||
|
||||
/// Builds an [lsp.SignatureHelp] for the given type parameters.
|
||||
lsp.SignatureHelp? _toSignatureHelp(
|
||||
String label,
|
||||
String? documentation,
|
||||
List<TypeParameterElement> typeParameters,
|
||||
) {
|
||||
final parameters = typeParameters
|
||||
.map((param) => lsp.ParameterInformation(
|
||||
label: param.getDisplayString(
|
||||
withNullability: _isNonNullableByDefault),
|
||||
))
|
||||
.toList();
|
||||
|
||||
final signatures = [
|
||||
lsp.SignatureInformation(
|
||||
label: label,
|
||||
documentation: documentation != null
|
||||
? asStringOrMarkupContent(preferredFormats, documentation)
|
||||
: null,
|
||||
parameters: parameters,
|
||||
),
|
||||
];
|
||||
|
||||
return lsp.SignatureHelp(
|
||||
signatures: signatures,
|
||||
activeSignature: 0,
|
||||
// As with [toSignatureHelp], we don't currently use this, but need to set
|
||||
// it to something that doesn't match a parameter to avoid one being
|
||||
// selected.
|
||||
activeParameter: -1,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -5,9 +5,12 @@
|
|||
import 'package:analysis_server/lsp_protocol/protocol_generated.dart';
|
||||
import 'package:analysis_server/lsp_protocol/protocol_special.dart';
|
||||
import 'package:analysis_server/src/computer/computer_signature.dart';
|
||||
import 'package:analysis_server/src/computer/computer_type_arguments_signature.dart';
|
||||
import 'package:analysis_server/src/lsp/handlers/handlers.dart';
|
||||
import 'package:analysis_server/src/lsp/lsp_analysis_server.dart';
|
||||
import 'package:analysis_server/src/lsp/mapping.dart';
|
||||
import 'package:analyzer/dart/ast/ast.dart';
|
||||
import 'package:analyzer/src/dartdoc/dartdoc_directive_info.dart';
|
||||
|
||||
class SignatureHelpHandler
|
||||
extends MessageHandler<SignatureHelpParams, SignatureHelp?> {
|
||||
|
@ -52,10 +55,24 @@ class SignatureHelpHandler
|
|||
final offset = await unit.mapResult((unit) => toOffset(unit.lineInfo, pos));
|
||||
|
||||
return offset.mapResult((offset) {
|
||||
final computer = DartUnitSignatureComputer(
|
||||
server.getDartdocDirectiveInfoFor(unit.result),
|
||||
unit.result.unit!,
|
||||
offset);
|
||||
final formats = clientCapabilities.signatureHelpDocumentationFormats;
|
||||
final dartDocInfo = server.getDartdocDirectiveInfoFor(unit.result);
|
||||
|
||||
// First check if we're in a type args list and if so build some
|
||||
// signature help for that.
|
||||
final typeArgsSignature = _tryGetTypeArgsSignatureHelp(
|
||||
dartDocInfo,
|
||||
unit.result.unit!,
|
||||
offset,
|
||||
autoTriggered,
|
||||
formats,
|
||||
);
|
||||
if (typeArgsSignature != null) {
|
||||
return success(typeArgsSignature);
|
||||
}
|
||||
|
||||
final computer =
|
||||
DartUnitSignatureComputer(dartDocInfo, unit.result.unit!, offset);
|
||||
if (!computer.offsetIsValid) {
|
||||
return success(null); // No error, just no valid hover.
|
||||
}
|
||||
|
@ -72,8 +89,39 @@ class SignatureHelpHandler
|
|||
return success(null);
|
||||
}
|
||||
|
||||
final formats = clientCapabilities.signatureHelpDocumentationFormats;
|
||||
return success(toSignatureHelp(formats, signature));
|
||||
});
|
||||
}
|
||||
|
||||
/// Tries to create signature information for a surrounding [TypeArgumentList].
|
||||
///
|
||||
/// Returns `null` if [offset] is in an invalid location, not inside a type
|
||||
/// argument list or was auto-triggered in a location that was not the start
|
||||
/// of a type argument list.
|
||||
SignatureHelp? _tryGetTypeArgsSignatureHelp(
|
||||
DartdocDirectiveInfo dartDocInfo,
|
||||
CompilationUnit unit,
|
||||
int offset,
|
||||
bool autoTriggered,
|
||||
Set<MarkupKind>? formats,
|
||||
) {
|
||||
final typeArgsComputer =
|
||||
DartTypeArgumentsSignatureComputer(dartDocInfo, unit, offset, formats);
|
||||
if (!typeArgsComputer.offsetIsValid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final typeSignature = typeArgsComputer.compute();
|
||||
if (typeSignature == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If auto-triggered from typing a `<`, only show if that `<` was at
|
||||
// the start of the arg list (to avoid triggering on other `<`s).
|
||||
if (autoTriggered && offset != typeArgsComputer.argumentList.offset + 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return typeSignature;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -465,6 +465,86 @@ void f() {
|
|||
));
|
||||
}
|
||||
|
||||
Future<void> test_typeParams_class() async {
|
||||
final content = '''
|
||||
/// My Foo.
|
||||
class Foo<T1, T2 extends String> {}
|
||||
|
||||
class Bar extends Foo<^> {}
|
||||
''';
|
||||
|
||||
const expectedLabel = 'class Foo<T1, T2 extends String>';
|
||||
const expectedDoc = 'My Foo.';
|
||||
|
||||
await initialize(
|
||||
textDocumentCapabilities: withSignatureHelpContentFormat(
|
||||
emptyTextDocumentClientCapabilities, [MarkupKind.Markdown]));
|
||||
await openFile(mainFileUri, withoutMarkers(content));
|
||||
await testSignature(
|
||||
content,
|
||||
expectedLabel,
|
||||
expectedDoc,
|
||||
[
|
||||
ParameterInformation(label: 'T1'),
|
||||
ParameterInformation(label: 'T2 extends String'),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_typeParams_function() async {
|
||||
final content = '''
|
||||
/// My Foo.
|
||||
void foo<T1, T2 extends String>() {
|
||||
foo<^>();
|
||||
}
|
||||
''';
|
||||
|
||||
const expectedLabel = 'void foo<T1, T2 extends String>()';
|
||||
const expectedDoc = 'My Foo.';
|
||||
|
||||
await initialize(
|
||||
textDocumentCapabilities: withSignatureHelpContentFormat(
|
||||
emptyTextDocumentClientCapabilities, [MarkupKind.Markdown]));
|
||||
await openFile(mainFileUri, withoutMarkers(content));
|
||||
await testSignature(
|
||||
content,
|
||||
expectedLabel,
|
||||
expectedDoc,
|
||||
[
|
||||
ParameterInformation(label: 'T1'),
|
||||
ParameterInformation(label: 'T2 extends String'),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_typeParams_method() async {
|
||||
final content = '''
|
||||
class Foo {
|
||||
/// My Foo.
|
||||
void foo<T1, T2 extends String>() {
|
||||
foo<^>();
|
||||
}
|
||||
}
|
||||
''';
|
||||
|
||||
const expectedLabel = 'void foo<T1, T2 extends String>()';
|
||||
const expectedDoc = 'My Foo.';
|
||||
|
||||
await initialize(
|
||||
textDocumentCapabilities: withSignatureHelpContentFormat(
|
||||
emptyTextDocumentClientCapabilities, [MarkupKind.Markdown]));
|
||||
await openFile(mainFileUri, withoutMarkers(content));
|
||||
await testSignature(
|
||||
content,
|
||||
expectedLabel,
|
||||
expectedDoc,
|
||||
[
|
||||
ParameterInformation(label: 'T1'),
|
||||
ParameterInformation(label: 'T2 extends String'),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> test_unopenFile() async {
|
||||
final content = '''
|
||||
/// Does foo.
|
||||
|
|
Loading…
Reference in a new issue