mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
323 lines
11 KiB
C++
323 lines
11 KiB
C++
//===--- REPLCodeCompletion.cpp - Code completion for REPL ----------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See http://swift.org/LICENSE.txt for license information
|
|
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This module provides completions to the immediate mode environment.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "swift/IDE/REPLCodeCompletion.h"
|
|
#include "swift/AST/ASTContext.h"
|
|
#include "swift/AST/Module.h"
|
|
#include "swift/Basic/LLVM.h"
|
|
#include "swift/Basic/SourceManager.h"
|
|
#include "swift/Parse/DelayedParsingCallbacks.h"
|
|
#include "swift/Parse/Parser.h"
|
|
#include "swift/IDE/CodeCompletion.h"
|
|
#include "swift/Subsystems.h"
|
|
#include "llvm/Support/raw_ostream.h"
|
|
#include "llvm/Support/MemoryBuffer.h"
|
|
#include "llvm/ADT/SmallString.h"
|
|
#include <algorithm>
|
|
|
|
using namespace swift;
|
|
using namespace ide;
|
|
|
|
static std::string toInsertableString(CodeCompletionResult *Result) {
|
|
std::string Str;
|
|
for (auto C : Result->getCompletionString()->getChunks()) {
|
|
switch (C.getKind()) {
|
|
case CodeCompletionString::Chunk::ChunkKind::AccessControlKeyword:
|
|
case CodeCompletionString::Chunk::ChunkKind::OverrideKeyword:
|
|
case CodeCompletionString::Chunk::ChunkKind::ThrowsKeyword:
|
|
case CodeCompletionString::Chunk::ChunkKind::RethrowsKeyword:
|
|
case CodeCompletionString::Chunk::ChunkKind::DeclAttrKeyword:
|
|
case CodeCompletionString::Chunk::ChunkKind::DeclIntroducer:
|
|
case CodeCompletionString::Chunk::ChunkKind::Text:
|
|
case CodeCompletionString::Chunk::ChunkKind::LeftParen:
|
|
case CodeCompletionString::Chunk::ChunkKind::RightParen:
|
|
case CodeCompletionString::Chunk::ChunkKind::LeftBracket:
|
|
case CodeCompletionString::Chunk::ChunkKind::RightBracket:
|
|
case CodeCompletionString::Chunk::ChunkKind::LeftAngle:
|
|
case CodeCompletionString::Chunk::ChunkKind::RightAngle:
|
|
case CodeCompletionString::Chunk::ChunkKind::Dot:
|
|
case CodeCompletionString::Chunk::ChunkKind::Ellipsis:
|
|
case CodeCompletionString::Chunk::ChunkKind::Comma:
|
|
case CodeCompletionString::Chunk::ChunkKind::ExclamationMark:
|
|
case CodeCompletionString::Chunk::ChunkKind::QuestionMark:
|
|
case CodeCompletionString::Chunk::ChunkKind::Ampersand:
|
|
case CodeCompletionString::Chunk::ChunkKind::Equal:
|
|
case CodeCompletionString::Chunk::ChunkKind::Whitespace:
|
|
case CodeCompletionString::Chunk::ChunkKind::DynamicLookupMethodCallTail:
|
|
case CodeCompletionString::Chunk::ChunkKind::OptionalMethodCallTail:
|
|
if (!C.isAnnotation())
|
|
Str += C.getText();
|
|
break;
|
|
|
|
case CodeCompletionString::Chunk::ChunkKind::CallParameterName:
|
|
case CodeCompletionString::Chunk::ChunkKind::CallParameterInternalName:
|
|
case CodeCompletionString::Chunk::ChunkKind::CallParameterColon:
|
|
case CodeCompletionString::Chunk::ChunkKind::DeclAttrParamKeyword:
|
|
case CodeCompletionString::Chunk::ChunkKind::DeclAttrParamColon:
|
|
case CodeCompletionString::Chunk::ChunkKind::CallParameterType:
|
|
case CodeCompletionString::Chunk::ChunkKind::CallParameterClosureType:
|
|
case CodeCompletionString::Chunk::ChunkKind::OptionalBegin:
|
|
case CodeCompletionString::Chunk::ChunkKind::CallParameterBegin:
|
|
case CodeCompletionString::Chunk::ChunkKind::GenericParameterBegin:
|
|
case CodeCompletionString::Chunk::ChunkKind::GenericParameterName:
|
|
case CodeCompletionString::Chunk::ChunkKind::TypeAnnotation:
|
|
return Str;
|
|
|
|
case CodeCompletionString::Chunk::ChunkKind::BraceStmtWithCursor:
|
|
Str += " {";
|
|
break;
|
|
}
|
|
}
|
|
return Str;
|
|
}
|
|
|
|
static void toDisplayString(CodeCompletionResult *Result,
|
|
llvm::raw_ostream &OS) {
|
|
std::string Str;
|
|
for (auto C : Result->getCompletionString()->getChunks()) {
|
|
if (C.getKind() ==
|
|
CodeCompletionString::Chunk::ChunkKind::BraceStmtWithCursor) {
|
|
OS << ' ';
|
|
continue;
|
|
}
|
|
if (!C.isAnnotation() && C.hasText()) {
|
|
OS << C.getText();
|
|
continue;
|
|
}
|
|
if (C.getKind() == CodeCompletionString::Chunk::ChunkKind::TypeAnnotation) {
|
|
if (Result->getKind() == CodeCompletionResult::Declaration) {
|
|
switch (Result->getAssociatedDeclKind()) {
|
|
case CodeCompletionDeclKind::Module:
|
|
case CodeCompletionDeclKind::PrecedenceGroup:
|
|
case CodeCompletionDeclKind::Class:
|
|
case CodeCompletionDeclKind::Struct:
|
|
case CodeCompletionDeclKind::Enum:
|
|
continue;
|
|
|
|
case CodeCompletionDeclKind::EnumElement:
|
|
OS << ": ";
|
|
break;
|
|
|
|
case CodeCompletionDeclKind::Protocol:
|
|
case CodeCompletionDeclKind::TypeAlias:
|
|
case CodeCompletionDeclKind::AssociatedType:
|
|
case CodeCompletionDeclKind::GenericTypeParam:
|
|
case CodeCompletionDeclKind::Constructor:
|
|
case CodeCompletionDeclKind::Destructor:
|
|
continue;
|
|
|
|
case CodeCompletionDeclKind::Subscript:
|
|
case CodeCompletionDeclKind::StaticMethod:
|
|
case CodeCompletionDeclKind::InstanceMethod:
|
|
case CodeCompletionDeclKind::PrefixOperatorFunction:
|
|
case CodeCompletionDeclKind::PostfixOperatorFunction:
|
|
case CodeCompletionDeclKind::InfixOperatorFunction:
|
|
case CodeCompletionDeclKind::FreeFunction:
|
|
OS << " -> ";
|
|
break;
|
|
|
|
case CodeCompletionDeclKind::StaticVar:
|
|
case CodeCompletionDeclKind::InstanceVar:
|
|
case CodeCompletionDeclKind::LocalVar:
|
|
case CodeCompletionDeclKind::GlobalVar:
|
|
OS << ": ";
|
|
break;
|
|
}
|
|
} else {
|
|
OS << ": ";
|
|
}
|
|
OS << C.getText();
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace swift {
|
|
class REPLCodeCompletionConsumer : public SimpleCachingCodeCompletionConsumer {
|
|
REPLCompletions &Completions;
|
|
|
|
public:
|
|
REPLCodeCompletionConsumer(REPLCompletions &Completions)
|
|
: Completions(Completions) {}
|
|
|
|
void handleResults(MutableArrayRef<CodeCompletionResult *> Results) override {
|
|
CodeCompletionContext::sortCompletionResults(Results);
|
|
for (auto Result : Results) {
|
|
std::string InsertableString = toInsertableString(Result);
|
|
if (StringRef(InsertableString).startswith(Completions.Prefix)) {
|
|
llvm::SmallString<128> PrintedResult;
|
|
{
|
|
llvm::raw_svector_ostream OS(PrintedResult);
|
|
toDisplayString(Result, OS);
|
|
}
|
|
Completions.CompletionStrings.push_back(
|
|
Completions.CompletionContext.copyString(PrintedResult));
|
|
|
|
InsertableString = InsertableString.substr(Completions.Prefix.size());
|
|
|
|
Completions.CookedResults.push_back(
|
|
{ Completions.CompletionContext.copyString(InsertableString),
|
|
Result->getNumBytesToErase() });
|
|
}
|
|
}
|
|
}
|
|
};
|
|
} // namespace swift
|
|
|
|
REPLCompletions::REPLCompletions()
|
|
: State(CompletionState::Invalid), CompletionContext(CompletionCache) {
|
|
// Create a CodeCompletionConsumer.
|
|
Consumer.reset(new REPLCodeCompletionConsumer(*this));
|
|
|
|
// Create a factory for code completion callbacks that will feed the
|
|
// Consumer.
|
|
CompletionCallbacksFactory.reset(
|
|
ide::makeCodeCompletionCallbacksFactory(CompletionContext,
|
|
*Consumer.get()));
|
|
}
|
|
|
|
static void
|
|
doCodeCompletion(SourceFile &SF, StringRef EnteredCode, unsigned *BufferID,
|
|
CodeCompletionCallbacksFactory *CompletionCallbacksFactory) {
|
|
// Temporarily disable printing the diagnostics.
|
|
ASTContext &Ctx = SF.getASTContext();
|
|
auto DiagnosticConsumers = Ctx.Diags.takeConsumers();
|
|
|
|
std::string AugmentedCode = EnteredCode.str();
|
|
AugmentedCode += '\0';
|
|
*BufferID = Ctx.SourceMgr.addMemBufferCopy(AugmentedCode, "<REPL Input>");
|
|
|
|
const unsigned CodeCompletionOffset = AugmentedCode.size() - 1;
|
|
|
|
Ctx.SourceMgr.setCodeCompletionPoint(*BufferID, CodeCompletionOffset);
|
|
|
|
// Parse, typecheck and temporarily insert the incomplete code into the AST.
|
|
const unsigned OriginalDeclCount = SF.Decls.size();
|
|
|
|
unsigned CurElem = OriginalDeclCount;
|
|
PersistentParserState PersistentState;
|
|
std::unique_ptr<DelayedParsingCallbacks> DelayedCB(
|
|
new CodeCompleteDelayedCallbacks(Ctx.SourceMgr.getCodeCompletionLoc()));
|
|
bool Done;
|
|
do {
|
|
parseIntoSourceFile(SF, *BufferID, &Done, nullptr, &PersistentState,
|
|
DelayedCB.get());
|
|
performTypeChecking(SF, PersistentState.getTopLevelContext(), None,
|
|
CurElem);
|
|
CurElem = SF.Decls.size();
|
|
} while (!Done);
|
|
|
|
performDelayedParsing(&SF, PersistentState, CompletionCallbacksFactory);
|
|
|
|
// Now we are done with code completion. Remove the declarations we
|
|
// temporarily inserted.
|
|
SF.Decls.resize(OriginalDeclCount);
|
|
|
|
// Add the diagnostic consumers back.
|
|
for (auto DC : DiagnosticConsumers)
|
|
Ctx.Diags.addConsumer(*DC);
|
|
|
|
Ctx.Diags.resetHadAnyError();
|
|
}
|
|
|
|
void REPLCompletions::populate(SourceFile &SF, StringRef EnteredCode) {
|
|
Prefix = "";
|
|
Root.reset();
|
|
CurrentCompletionIdx = ~size_t(0);
|
|
|
|
CompletionStrings.clear();
|
|
CookedResults.clear();
|
|
|
|
assert(SF.Kind == SourceFileKind::REPL && "Can't append to a non-REPL file");
|
|
|
|
unsigned BufferID;
|
|
doCodeCompletion(SF, EnteredCode, &BufferID,
|
|
CompletionCallbacksFactory.get());
|
|
|
|
ASTContext &Ctx = SF.getASTContext();
|
|
std::vector<Token> Tokens = tokenize(Ctx.LangOpts, Ctx.SourceMgr, BufferID);
|
|
|
|
if (!Tokens.empty() && Tokens.back().is(tok::code_complete))
|
|
Tokens.pop_back();
|
|
|
|
if (!Tokens.empty()) {
|
|
Token &LastToken = Tokens.back();
|
|
if (LastToken.is(tok::identifier) || LastToken.isKeyword()) {
|
|
Prefix = LastToken.getText();
|
|
|
|
unsigned Offset = Ctx.SourceMgr.getLocOffsetInBuffer(LastToken.getLoc(),
|
|
BufferID);
|
|
|
|
doCodeCompletion(SF, EnteredCode.substr(0, Offset),
|
|
&BufferID, CompletionCallbacksFactory.get());
|
|
}
|
|
}
|
|
|
|
if (CookedResults.empty())
|
|
State = CompletionState::Empty;
|
|
else if (CookedResults.size() == 1)
|
|
State = CompletionState::Unique;
|
|
else
|
|
State = CompletionState::CompletedRoot;
|
|
}
|
|
|
|
StringRef REPLCompletions::getRoot() const {
|
|
if (Root)
|
|
return Root.getValue();
|
|
|
|
if (CookedResults.empty()) {
|
|
Root = std::string();
|
|
return Root.getValue();
|
|
}
|
|
|
|
std::string RootStr = CookedResults[0].InsertableString;
|
|
for (auto R : CookedResults) {
|
|
if (R.NumBytesToErase != 0) {
|
|
RootStr.resize(0);
|
|
break;
|
|
}
|
|
auto MismatchPlace = std::mismatch(RootStr.begin(), RootStr.end(),
|
|
R.InsertableString.begin());
|
|
RootStr.resize(MismatchPlace.first - RootStr.begin());
|
|
}
|
|
Root = RootStr;
|
|
return Root.getValue();
|
|
}
|
|
|
|
REPLCompletions::CookedResult REPLCompletions::getPreviousStem() const {
|
|
if (CurrentCompletionIdx == ~size_t(0) || CookedResults.empty())
|
|
return {};
|
|
|
|
const auto &Result = CookedResults[CurrentCompletionIdx];
|
|
return { Result.InsertableString.substr(getRoot().size()),
|
|
Result.NumBytesToErase };
|
|
}
|
|
|
|
REPLCompletions::CookedResult REPLCompletions::getNextStem() {
|
|
if (CookedResults.empty())
|
|
return {};
|
|
|
|
CurrentCompletionIdx++;
|
|
if (CurrentCompletionIdx >= CookedResults.size())
|
|
CurrentCompletionIdx = 0;
|
|
|
|
const auto &Result = CookedResults[CurrentCompletionIdx];
|
|
return { Result.InsertableString.substr(getRoot().size()),
|
|
Result.NumBytesToErase };
|
|
}
|
|
|
|
void REPLCompletions::reset() { State = CompletionState::Invalid; }
|
|
|