| //===--- 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::ThrowsKeyword: |
| case ChunkKind::RethrowsKeyword: |
| 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(); |
| } |
| } |
| } |