mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
Consolidate ThrowsKeyword, RethrowsKeyword, and AsyncKeyword to EffectsSpecifierKeyword. Abolish 'key.throwsoffset' and 'key.throwslength' as they aren't used.
441 lines
14 KiB
C++
441 lines
14 KiB
C++
//===--- CodeCompletionResultPrinter.cpp ----------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2020 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "swift/IDE/CodeCompletionResultPrinter.h"
|
|
#include "swift/AST/ASTPrinter.h"
|
|
#include "swift/Basic/LLVM.h"
|
|
#include "swift/IDE/CodeCompletion.h"
|
|
#include "swift/Markup/XMLUtils.h"
|
|
#include "llvm/Support/raw_ostream.h"
|
|
|
|
using namespace swift;
|
|
using namespace swift::ide;
|
|
|
|
using ChunkKind = CodeCompletionString::Chunk::ChunkKind;
|
|
|
|
void swift::ide::printCodeCompletionResultDescription(
|
|
const CodeCompletionResult &result,
|
|
|
|
raw_ostream &OS, bool leadingPunctuation) {
|
|
auto str = result.getCompletionString();
|
|
bool isOperator = result.isOperator();
|
|
|
|
auto FirstTextChunk = str->getFirstTextChunkIndex(leadingPunctuation);
|
|
int TextSize = 0;
|
|
if (FirstTextChunk.hasValue()) {
|
|
auto Chunks = str->getChunks().slice(*FirstTextChunk);
|
|
for (auto I = Chunks.begin(), E = Chunks.end(); I != E; ++I) {
|
|
const auto &C = *I;
|
|
|
|
using ChunkKind = CodeCompletionString::Chunk::ChunkKind;
|
|
|
|
if (C.is(ChunkKind::TypeAnnotation) ||
|
|
C.is(ChunkKind::CallParameterClosureType) ||
|
|
C.is(ChunkKind::CallParameterClosureExpr) ||
|
|
C.is(ChunkKind::Whitespace))
|
|
continue;
|
|
|
|
// Skip TypeAnnotation group.
|
|
if (C.is(ChunkKind::TypeAnnotationBegin)) {
|
|
auto level = I->getNestingLevel();
|
|
do { ++I; } while (I != E && !I->endsPreviousNestedGroup(level));
|
|
--I;
|
|
continue;
|
|
}
|
|
|
|
if (isOperator && C.is(ChunkKind::CallParameterType))
|
|
continue;
|
|
if (isOperator && C.is(ChunkKind::CallParameterTypeBegin)) {
|
|
auto level = I->getNestingLevel();
|
|
do { ++I; } while (I != E && !I->endsPreviousNestedGroup(level));
|
|
--I;
|
|
continue;
|
|
}
|
|
|
|
if (C.hasText()) {
|
|
TextSize += C.getText().size();
|
|
OS << C.getText();
|
|
}
|
|
}
|
|
}
|
|
assert((TextSize > 0) &&
|
|
"code completion result should have non-empty description!");
|
|
}
|
|
|
|
namespace {
|
|
class AnnotatingResultPrinter {
|
|
raw_ostream &OS;
|
|
|
|
/// Print \p content enclosing with \p tag.
|
|
void printWithTag(StringRef tag, StringRef content) {
|
|
// Trim whitepsaces around the non-whitespace characters.
|
|
// (i.e. " something " -> " <tag>something</tag> ".
|
|
auto ltrimIdx = content.find_first_not_of(' ');
|
|
auto rtrimIdx = content.find_last_not_of(' ') + 1;
|
|
assert(ltrimIdx != StringRef::npos && rtrimIdx != StringRef::npos &&
|
|
"empty or whitespace only element");
|
|
|
|
OS << content.substr(0, ltrimIdx);
|
|
OS << "<" << tag << ">";
|
|
swift::markup::appendWithXMLEscaping(
|
|
OS, content.substr(ltrimIdx, rtrimIdx - ltrimIdx));
|
|
OS << "</" << tag << ">";
|
|
OS << content.substr(rtrimIdx);
|
|
}
|
|
|
|
void printTextChunk(CodeCompletionString::Chunk C) {
|
|
if (!C.hasText())
|
|
return;
|
|
|
|
switch (C.getKind()) {
|
|
case ChunkKind::Keyword:
|
|
case ChunkKind::OverrideKeyword:
|
|
case ChunkKind::AccessControlKeyword:
|
|
case ChunkKind::EffectsSpecifierKeyword:
|
|
case ChunkKind::DeclIntroducer:
|
|
printWithTag("keyword", C.getText());
|
|
break;
|
|
case ChunkKind::DeclAttrKeyword:
|
|
case ChunkKind::Attribute:
|
|
printWithTag("attribute", C.getText());
|
|
break;
|
|
case ChunkKind::BaseName:
|
|
printWithTag("name", C.getText());
|
|
break;
|
|
case ChunkKind::TypeIdSystem:
|
|
printWithTag("typeid.sys", C.getText());
|
|
break;
|
|
case ChunkKind::TypeIdUser:
|
|
printWithTag("typeid.user", C.getText());
|
|
break;
|
|
case ChunkKind::CallParameterName:
|
|
printWithTag("callarg.label", C.getText());
|
|
break;
|
|
case ChunkKind::CallParameterInternalName:
|
|
printWithTag("callarg.param", C.getText());
|
|
break;
|
|
case ChunkKind::TypeAnnotation:
|
|
case ChunkKind::CallParameterClosureType:
|
|
case ChunkKind::CallParameterClosureExpr:
|
|
case ChunkKind::Whitespace:
|
|
// ignore;
|
|
break;
|
|
default:
|
|
swift::markup::appendWithXMLEscaping(OS, C.getText());
|
|
break;
|
|
}
|
|
}
|
|
|
|
void printCallArg(ArrayRef<CodeCompletionString::Chunk> chunks) {
|
|
OS << "<callarg>";
|
|
for (auto i = chunks.begin(), e = chunks.end(); i != e; ++i) {
|
|
using ChunkKind = CodeCompletionString::Chunk::ChunkKind;
|
|
|
|
if (i->is(ChunkKind::CallParameterTypeBegin)) {
|
|
OS << "<callarg.type>";
|
|
auto nestingLevel = i->getNestingLevel();
|
|
++i;
|
|
for (; i != e; ++i) {
|
|
if (i->endsPreviousNestedGroup(nestingLevel))
|
|
break;
|
|
if (i->hasText())
|
|
printTextChunk(*i);
|
|
}
|
|
OS << "</callarg.type>";
|
|
if (i == e)
|
|
break;
|
|
}
|
|
|
|
printTextChunk(*i);
|
|
}
|
|
OS << "</callarg>";
|
|
}
|
|
|
|
public:
|
|
AnnotatingResultPrinter(raw_ostream &OS) : OS(OS) {}
|
|
|
|
void printDescription(const CodeCompletionResult &result, bool leadingPunctuation) {
|
|
auto str = result.getCompletionString();
|
|
bool isOperator = result.isOperator();
|
|
|
|
auto FirstTextChunk = str->getFirstTextChunkIndex(leadingPunctuation);
|
|
if (FirstTextChunk.hasValue()) {
|
|
auto chunks = str->getChunks().slice(*FirstTextChunk);
|
|
for (auto i = chunks.begin(), e = chunks.end(); i != e; ++i) {
|
|
using ChunkKind = CodeCompletionString::Chunk::ChunkKind;
|
|
|
|
// Skip the type annotation.
|
|
if (i->is(ChunkKind::TypeAnnotationBegin)) {
|
|
auto level = i->getNestingLevel();
|
|
do { ++i; } while (i != e && !i->endsPreviousNestedGroup(level));
|
|
--i;
|
|
continue;
|
|
}
|
|
|
|
// Print call argument group.
|
|
if (i->is(ChunkKind::CallParameterBegin)) {
|
|
auto start = i;
|
|
auto level = i->getNestingLevel();
|
|
do { ++i; } while (i != e && !i->endsPreviousNestedGroup(level));
|
|
if (!isOperator)
|
|
printCallArg({start, i});
|
|
--i;
|
|
continue;
|
|
}
|
|
|
|
if (isOperator && i->is(ChunkKind::CallParameterType))
|
|
continue;
|
|
printTextChunk(*i);
|
|
}
|
|
}
|
|
}
|
|
|
|
void printTypeName(const CodeCompletionResult &result) {
|
|
auto Chunks = result.getCompletionString()->getChunks();
|
|
|
|
for (auto i = Chunks.begin(), e = Chunks.end(); i != e; ++i) {
|
|
|
|
if (i->is(CodeCompletionString::Chunk::ChunkKind::TypeAnnotation))
|
|
OS << i->getText();
|
|
|
|
if (i->is(CodeCompletionString::Chunk::ChunkKind::TypeAnnotationBegin)) {
|
|
auto nestingLevel = i->getNestingLevel();
|
|
++i;
|
|
for (; i != e && !i->endsPreviousNestedGroup(nestingLevel); ++i) {
|
|
if (i->hasText())
|
|
printTextChunk(*i);
|
|
}
|
|
--i;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
void swift::ide::printCodeCompletionResultDescriptionAnnotated(
|
|
const CodeCompletionResult &Result, raw_ostream &OS,
|
|
bool leadingPunctuation) {
|
|
AnnotatingResultPrinter printer(OS);
|
|
printer.printDescription(Result, leadingPunctuation);
|
|
}
|
|
|
|
|
|
void swift::ide::printCodeCompletionResultTypeName(const CodeCompletionResult &Result,
|
|
llvm::raw_ostream &OS) {
|
|
auto Chunks = Result.getCompletionString()->getChunks();
|
|
|
|
for (auto i = Chunks.begin(), e = Chunks.end(); i != e; ++i) {
|
|
|
|
if (i->is(CodeCompletionString::Chunk::ChunkKind::TypeAnnotation))
|
|
OS << i->getText();
|
|
|
|
if (i->is(CodeCompletionString::Chunk::ChunkKind::TypeAnnotationBegin)) {
|
|
auto nestingLevel = i->getNestingLevel();
|
|
++i;
|
|
for (; i != e && !i->endsPreviousNestedGroup(nestingLevel); ++i) {
|
|
if (i->hasText())
|
|
OS << i->getText();
|
|
}
|
|
--i;
|
|
}
|
|
}
|
|
}
|
|
|
|
void swift::ide::printCodeCompletionResultTypeNameAnnotated(const CodeCompletionResult &Result, llvm::raw_ostream &OS) {
|
|
AnnotatingResultPrinter printer(OS);
|
|
printer.printTypeName(Result);
|
|
}
|
|
|
|
/// Provide the text for the call parameter, including constructing a typed
|
|
/// editor placeholder for it.
|
|
static void
|
|
constructTextForCallParam(ArrayRef<CodeCompletionString::Chunk> ParamGroup,
|
|
raw_ostream &OS) {
|
|
assert(ParamGroup.front().is(ChunkKind::CallParameterBegin));
|
|
|
|
for (; !ParamGroup.empty(); ParamGroup = ParamGroup.slice(1)) {
|
|
auto &C = ParamGroup.front();
|
|
if (C.isAnnotation())
|
|
continue;
|
|
if (C.is(ChunkKind::CallParameterInternalName) ||
|
|
C.is(ChunkKind::CallParameterType) ||
|
|
C.is(ChunkKind::CallParameterTypeBegin) ||
|
|
C.is(ChunkKind::CallParameterClosureExpr)) {
|
|
break;
|
|
}
|
|
if (!C.hasText())
|
|
continue;
|
|
OS << C.getText();
|
|
}
|
|
|
|
SmallString<32> DisplayString;
|
|
SmallString<32> TypeString;
|
|
SmallString<32> ExpansionTypeString;
|
|
|
|
for (auto i = ParamGroup.begin(), e = ParamGroup.end(); i != e; ++i) {
|
|
auto &C = *i;
|
|
if (C.is(ChunkKind::CallParameterTypeBegin)) {
|
|
assert(TypeString.empty());
|
|
auto nestingLevel = C.getNestingLevel();
|
|
++i;
|
|
for (; i != e; ++i) {
|
|
if (i->endsPreviousNestedGroup(nestingLevel))
|
|
break;
|
|
if (!i->isAnnotation() && i->hasText()) {
|
|
TypeString += i->getText();
|
|
DisplayString += i->getText();
|
|
}
|
|
}
|
|
--i;
|
|
continue;
|
|
}
|
|
if (C.is(ChunkKind::CallParameterClosureType)) {
|
|
assert(ExpansionTypeString.empty());
|
|
ExpansionTypeString = C.getText();
|
|
continue;
|
|
}
|
|
if (C.is(ChunkKind::CallParameterType)) {
|
|
assert(TypeString.empty());
|
|
TypeString = C.getText();
|
|
}
|
|
if (C.is(ChunkKind::CallParameterClosureExpr)) {
|
|
// We have a closure expression, so provide it directly instead of in
|
|
// a placeholder.
|
|
OS << "{";
|
|
if (!C.getText().empty())
|
|
OS << " " << C.getText();
|
|
OS << "\n" << getCodePlaceholder() << "\n}";
|
|
return;
|
|
}
|
|
if (C.isAnnotation() || !C.hasText())
|
|
continue;
|
|
DisplayString += C.getText();
|
|
}
|
|
|
|
StringRef Display = DisplayString.str();
|
|
StringRef Type = TypeString.str();
|
|
StringRef ExpansionType = ExpansionTypeString.str();
|
|
if (ExpansionType.empty())
|
|
ExpansionType = Type;
|
|
|
|
OS << "<#T##" << Display;
|
|
if (Display == Type && Display == ExpansionType) {
|
|
// Short version, display and type are the same.
|
|
} else {
|
|
OS << "##" << Type;
|
|
if (ExpansionType != Type)
|
|
OS << "##" << ExpansionType;
|
|
}
|
|
OS << "#>";
|
|
}
|
|
|
|
void swift::ide::printCodeCompletionResultSourceText(
|
|
const CodeCompletionResult &Result, llvm::raw_ostream &OS) {
|
|
auto Chunks = Result.getCompletionString()->getChunks();
|
|
for (size_t i = 0; i < Chunks.size(); ++i) {
|
|
auto &C = Chunks[i];
|
|
if (C.is(ChunkKind::BraceStmtWithCursor)) {
|
|
OS << " {\n" << getCodePlaceholder() << "\n}";
|
|
continue;
|
|
}
|
|
if (C.is(ChunkKind::CallParameterBegin)) {
|
|
size_t Start = i++;
|
|
for (; i < Chunks.size(); ++i) {
|
|
if (Chunks[i].endsPreviousNestedGroup(C.getNestingLevel()))
|
|
break;
|
|
}
|
|
constructTextForCallParam(Chunks.slice(Start, i - Start), OS);
|
|
--i;
|
|
continue;
|
|
}
|
|
if (C.is(ChunkKind::TypeAnnotationBegin)) {
|
|
// Skip type annotation structure.
|
|
auto level = C.getNestingLevel();
|
|
do {
|
|
++i;
|
|
} while (i != Chunks.size() && !Chunks[i].endsPreviousNestedGroup(level));
|
|
--i;
|
|
}
|
|
if (!C.isAnnotation() && C.hasText()) {
|
|
OS << C.getText();
|
|
}
|
|
}
|
|
}
|
|
|
|
void swift::ide::printCodeCompletionResultFilterName(
|
|
const CodeCompletionResult &Result, llvm::raw_ostream &OS) {
|
|
auto str = Result.getCompletionString();
|
|
// FIXME: we need a more uniform way to handle operator completions.
|
|
if (str->getChunks().size() == 1 && str->getChunks()[0].is(ChunkKind::Dot)) {
|
|
OS << ".";
|
|
return;
|
|
} else if (str->getChunks().size() == 2 &&
|
|
str->getChunks()[0].is(ChunkKind::QuestionMark) &&
|
|
str->getChunks()[1].is(ChunkKind::Dot)) {
|
|
OS << "?.";
|
|
return;
|
|
}
|
|
|
|
auto FirstTextChunk = str->getFirstTextChunkIndex();
|
|
if (FirstTextChunk.hasValue()) {
|
|
auto chunks = str->getChunks().slice(*FirstTextChunk);
|
|
for (auto i = chunks.begin(), e = chunks.end(); i != e; ++i) {
|
|
auto &C = *i;
|
|
|
|
if (C.is(ChunkKind::BraceStmtWithCursor))
|
|
break; // Don't include brace-stmt in filter name.
|
|
|
|
if (C.is(ChunkKind::Equal)) {
|
|
OS << C.getText();
|
|
break;
|
|
}
|
|
|
|
bool shouldPrint = !C.isAnnotation();
|
|
switch (C.getKind()) {
|
|
case ChunkKind::TypeAnnotation:
|
|
case ChunkKind::CallParameterInternalName:
|
|
case ChunkKind::CallParameterClosureType:
|
|
case ChunkKind::CallParameterClosureExpr:
|
|
case ChunkKind::CallParameterType:
|
|
case ChunkKind::DeclAttrParamColon:
|
|
case ChunkKind::Comma:
|
|
case ChunkKind::Whitespace:
|
|
case ChunkKind::Ellipsis:
|
|
case ChunkKind::Ampersand:
|
|
case ChunkKind::OptionalMethodCallTail:
|
|
continue;
|
|
case ChunkKind::CallParameterTypeBegin:
|
|
case ChunkKind::TypeAnnotationBegin: {
|
|
// Skip call parameter type or type annotation structure.
|
|
auto nestingLevel = C.getNestingLevel();
|
|
do {
|
|
++i;
|
|
} while (i != e && !i->endsPreviousNestedGroup(nestingLevel));
|
|
--i;
|
|
continue;
|
|
}
|
|
case ChunkKind::CallParameterColon:
|
|
// Since we don't add the type, also don't add the space after ':'.
|
|
if (shouldPrint)
|
|
OS << ":";
|
|
continue;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (C.hasText() && shouldPrint)
|
|
OS << C.getText();
|
|
}
|
|
}
|
|
}
|