blob: 0f03a2569707c7d4f8f9df233008ebba64b5fdde [file] [log] [blame]
// Copyright (c) 2022, 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/src/protocol_server.dart';
import 'package:collection/collection.dart';
import 'completion_check.dart';
class CompletionResponsePrinter {
final StringBuffer buffer;
final Configuration configuration;
final CompletionResponseForTesting response;
String _indent = '';
CompletionResponsePrinter({
required this.buffer,
required this.configuration,
required this.response,
});
void writeResponse() {
_writeLocation();
_writeResponseReplacement();
_writeSuggestions();
}
/// Compares suggestions according to the configuration sorting.
int _compareSuggestions(CompletionSuggestion a, CompletionSuggestion b) {
int completionThenKind() {
var completionDiff = a.completion.compareTo(b.completion);
if (completionDiff != 0) {
return completionDiff;
} else {
return a.kind.name.compareTo(b.kind.name);
}
}
switch (configuration.sorting) {
case Sorting.asIs:
return 0;
case Sorting.completionThenKind:
return completionThenKind();
case Sorting.relevanceThenCompletionThenKind:
var relevanceDiff = a.relevance - b.relevance;
if (relevanceDiff != 0) {
return -relevanceDiff;
} else {
return completionThenKind();
}
}
}
String _escapeMultiLine(String text) {
return text.replaceAll('\n', r'\n');
}
String _getElementKindName(ElementKind kind) {
if (kind == ElementKind.CLASS) {
return 'class';
} else if (kind == ElementKind.CONSTRUCTOR) {
return 'constructor';
} else if (kind == ElementKind.ENUM) {
return 'enum';
} else if (kind == ElementKind.EXTENSION) {
return 'extension';
} else if (kind == ElementKind.FIELD) {
return 'field';
} else if (kind == ElementKind.FUNCTION) {
return 'function';
} else if (kind == ElementKind.PARAMETER) {
return 'parameter';
} else if (kind == ElementKind.TOP_LEVEL_VARIABLE) {
return 'topLevelVariable';
} else if (kind == ElementKind.TYPE_ALIAS) {
return 'typeAlias';
}
throw UnimplementedError('kind: $kind');
}
String _getSuggestionKindName(CompletionSuggestion suggestion) {
var kind = suggestion.kind;
if (kind == CompletionSuggestionKind.KEYWORD) {
return 'keyword';
} else if (kind == CompletionSuggestionKind.IDENTIFIER) {
var elementKind = suggestion.element?.kind;
if (elementKind == null) {
return 'identifier';
} else if (elementKind == ElementKind.CLASS) {
return 'class';
} else if (elementKind == ElementKind.CONSTRUCTOR) {
return 'constructor';
} else if (elementKind == ElementKind.ENUM) {
return 'enum';
} else if (elementKind == ElementKind.ENUM_CONSTANT) {
return 'enumConstant';
} else if (elementKind == ElementKind.EXTENSION) {
return 'extension';
} else if (elementKind == ElementKind.EXTENSION_TYPE) {
return 'extensionType';
} else if (elementKind == ElementKind.FIELD) {
return 'field';
} else if (elementKind == ElementKind.FUNCTION) {
return 'function';
} else if (elementKind == ElementKind.GETTER) {
return 'getter';
} else if (elementKind == ElementKind.LABEL) {
return 'label';
} else if (elementKind == ElementKind.LIBRARY) {
return 'library';
} else if (elementKind == ElementKind.LOCAL_VARIABLE) {
return 'localVariable';
} else if (elementKind == ElementKind.METHOD) {
return 'method';
} else if (elementKind == ElementKind.MIXIN) {
return 'mixin';
} else if (elementKind == ElementKind.PARAMETER) {
return 'parameter';
} else if (elementKind == ElementKind.SETTER) {
return 'setter';
} else if (elementKind == ElementKind.TOP_LEVEL_VARIABLE) {
return 'topLevelVariable';
} else if (elementKind == ElementKind.TYPE_ALIAS) {
return 'typeAlias';
} else if (elementKind == ElementKind.TYPE_PARAMETER) {
return 'typeParameter';
}
throw UnimplementedError('elementKind: $elementKind');
} else if (kind == CompletionSuggestionKind.INVOCATION) {
var elementKind = suggestion.element?.kind;
if (elementKind == null) {
return 'invocation';
} else if (elementKind == ElementKind.CONSTRUCTOR) {
return 'constructorInvocation';
} else if (elementKind == ElementKind.EXTENSION) {
return 'extensionInvocation';
} else if (elementKind == ElementKind.FUNCTION) {
return 'functionInvocation';
} else if (elementKind == ElementKind.METHOD) {
return 'methodInvocation';
}
throw UnimplementedError('elementKind: $elementKind');
} else if (kind == CompletionSuggestionKind.IMPORT) {
return 'import';
} else if (kind == CompletionSuggestionKind.NAMED_ARGUMENT) {
return 'namedArgument';
} else if (kind == CompletionSuggestionKind.OVERRIDE) {
return 'override';
}
throw UnimplementedError('kind: $kind');
}
void _withIndent(void Function() f) {
var indent = _indent;
_indent = '$_indent ';
f();
_indent = indent;
}
void _writeCompletion(CompletionSuggestion suggestion) {
var completion = suggestion.completion;
if (RegExp(r'^\s').hasMatch(completion) ||
RegExp(r'\s$').hasMatch(completion)) {
_writelnWithIndent('|$completion|');
} else {
_writelnWithIndent(completion);
}
}
void _writeDeclaringType(CompletionSuggestion suggestion) {
if (configuration.withDeclaringType) {
_writelnWithIndent('declaringType: ${suggestion.declaringType}');
}
}
void _writeDefaultArgumentList(CompletionSuggestion suggestion) {
if (configuration.withDefaultArgumentList) {
_writelnWithIndent(
'defaultArgumentList: ${suggestion.defaultArgumentListString}');
_writelnWithIndent(
'defaultArgumentListRanges: ${suggestion.defaultArgumentListTextRanges}');
}
}
void _writeDeprecated(CompletionSuggestion suggestion) {
if (suggestion.isDeprecated) {
_writelnWithIndent('deprecated: true');
}
}
void _writeDisplayText(CompletionSuggestion suggestion) {
if (configuration.withDisplayText) {
_writelnWithIndent('displayText: ${suggestion.displayText}');
}
}
void _writeDocumentation(CompletionSuggestion suggestion) {
if (configuration.withDocumentation) {
var docComplete = suggestion.docComplete;
if (docComplete != null) {
var text = _escapeMultiLine(docComplete);
_writelnWithIndent('docComplete: $text');
}
var docSummary = suggestion.docSummary;
if (docSummary != null) {
var text = _escapeMultiLine(docSummary);
_writelnWithIndent('docSummary: $text');
}
}
}
void _writeElement(CompletionSuggestion suggestion) {
if (configuration.withElement) {
var element = suggestion.element;
if (element != null) {
_writelnWithIndent('element');
_withIndent(() {
var kindStr = _getElementKindName(element.kind);
_writelnWithIndent('name: ${element.name}');
_writelnWithIndent('kind: $kindStr');
});
}
}
}
void _writeElementOffset(CompletionSuggestion suggestion) {
if (configuration.withElementOffset) {
var element = suggestion.element;
if (element != null) {
_writelnWithIndent('offset: ${element.location?.offset}');
}
}
}
void _writeIsNotImported(CompletionSuggestion suggestion) {
if (configuration.withIsNotImported) {
_writelnWithIndent('isNotImported: ${suggestion.isNotImported}');
}
}
void _writeLibraryUri(CompletionSuggestion suggestion) {
if (configuration.withLibraryUri) {
_writelnWithIndent('libraryUri: ${suggestion.libraryUri}');
}
}
void _writelnWithIndent(String line) {
buffer.write(_indent);
buffer.writeln(line);
}
void _writeLocation() {
if (configuration.withLocationName) {
if (response.requestLocationName case var location?) {
_writelnWithIndent('location: $location');
}
// TODO(scheglov): will be removed
if (response.opTypeLocationName case var location?) {
_writelnWithIndent('locationOpType: $location');
}
}
}
void _writeParameterNames(CompletionSuggestion suggestion) {
if (configuration.withParameterNames) {
var parameterNames = suggestion.parameterNames?.join(',') ?? '';
if (parameterNames.isNotEmpty) {
parameterNames = ' $parameterNames';
}
_writelnWithIndent('parameterNames:$parameterNames');
var parameterTypes = suggestion.parameterTypes?.join(',') ?? '';
if (parameterTypes.isNotEmpty) {
parameterTypes = ' $parameterTypes';
}
_writelnWithIndent('parameterTypes:$parameterTypes');
}
}
void _writeRelevance(CompletionSuggestion suggestion) {
if (configuration.withRelevance) {
_writelnWithIndent('relevance: ${suggestion.relevance}');
}
}
void _writeResponseReplacement() {
if (configuration.withReplacement) {
var offset = response.replacementOffset;
var length = response.replacementLength;
var left = response.requestOffset - offset;
var right = (offset + length) - response.requestOffset;
if (left > 0 || right > 0) {
_writelnWithIndent('replacement');
if (left > 0) {
_withIndent(() {
_writelnWithIndent('left: $left');
});
}
if (right > 0) {
_withIndent(() {
_writelnWithIndent('right: $right');
});
}
}
}
}
void _writeReturnType(CompletionSuggestion suggestion) {
if (configuration.withReturnType) {
var returnType = suggestion.returnType;
if (returnType != null) {
_writelnWithIndent('returnType: $returnType');
}
}
}
void _writeSelection(CompletionSuggestion suggestion) {
if (configuration.withSelection) {
var offset = suggestion.selectionOffset;
var length = suggestion.selectionLength;
if (length != 0) {
_writelnWithIndent('selection: $offset $length');
} else if (offset != suggestion.completion.length) {
_writelnWithIndent('selection: $offset');
}
}
}
void _writeSuggestion(CompletionSuggestion suggestion) {
_writeCompletion(suggestion);
_withIndent(() {
_writeSuggestionKind(suggestion);
_writeElementOffset(suggestion);
_writeDeclaringType(suggestion);
_writeDeprecated(suggestion);
_writeDefaultArgumentList(suggestion);
_writeDisplayText(suggestion);
_writeDocumentation(suggestion);
_writeElement(suggestion);
_writeIsNotImported(suggestion);
_writeLibraryUri(suggestion);
_writeParameterNames(suggestion);
_writeRelevance(suggestion);
_writeReturnType(suggestion);
_writeSelection(suggestion);
});
}
void _writeSuggestionKind(CompletionSuggestion suggestion) {
if (configuration.withKind) {
var kind = _getSuggestionKindName(suggestion);
_writelnWithIndent('kind: $kind');
}
}
void _writeSuggestions() {
var filtered = response.suggestions.where(configuration.filter);
var sorted = filtered.sorted(_compareSuggestions);
_writelnWithIndent('suggestions');
_withIndent(() {
for (var suggestion in sorted) {
if (configuration.filter(suggestion)) {
_writeSuggestion(suggestion);
}
}
});
}
}
class Configuration {
Sorting sorting;
bool withDeclaringType;
bool withDefaultArgumentList;
bool withDisplayText;
bool withDocumentation;
bool withElement;
bool withElementOffset;
bool withIsNotImported;
bool withKind;
bool withLibraryUri;
bool withLocationName;
bool withParameterNames;
bool withRelevance;
bool withReplacement;
bool withReturnType;
bool withSelection;
bool Function(CompletionSuggestion suggestion) filter;
Configuration({
this.sorting = Sorting.relevanceThenCompletionThenKind,
this.withDeclaringType = false,
this.withDefaultArgumentList = false,
this.withDisplayText = false,
this.withDocumentation = false,
this.withElement = false,
this.withElementOffset = false,
this.withIsNotImported = false,
this.withKind = true,
this.withLibraryUri = false,
this.withLocationName = false,
this.withParameterNames = false,
this.withReplacement = true,
this.withRelevance = false,
this.withReturnType = false,
this.withSelection = true,
required this.filter,
});
}
enum Sorting {
asIs,
completionThenKind,
relevanceThenCompletionThenKind,
}