Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/frontend/cxx/frontend.cc
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,15 @@
#include <cxx/lexer.h>
#include <cxx/lsp/lsp_server.h>
#include <cxx/macos_toolchain.h>
#include <cxx/name_printer.h>
#include <cxx/preprocessor.h>
#include <cxx/private/path.h>
#include <cxx/scope.h>
#include <cxx/symbol_printer.h>
#include <cxx/symbols.h>
#include <cxx/translation_unit.h>
#include <cxx/type_printer.h>
#include <cxx/types.h>
#include <cxx/wasm32_wasi_toolchain.h>
#include <cxx/windows_toolchain.h>

Expand Down
2 changes: 2 additions & 0 deletions src/frontend/cxx/verify_diagnostics_client.cc
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ auto VerifyDiagnosticsClient::hasErrors() const -> bool {
}

void VerifyDiagnosticsClient::report(const Diagnostic& diagnostic) {
if (!shouldReportErrors()) return;

if (verify_) {
reportedDiagnostics_.push_back(diagnostic);
return;
Expand Down
48 changes: 45 additions & 3 deletions src/lsp/cxx/lsp/cxx_document.cc
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,28 @@
#include <cxx/ast.h>
#include <cxx/control.h>
#include <cxx/gcc_linux_toolchain.h>
#include <cxx/lsp/enums.h>
#include <cxx/lsp/types.h>
#include <cxx/macos_toolchain.h>
#include <cxx/name_printer.h>
#include <cxx/preprocessor.h>
#include <cxx/private/path.h>
#include <cxx/scope.h>
#include <cxx/symbol_printer.h>
#include <cxx/symbols.h>
#include <cxx/translation_unit.h>
#include <cxx/type_printer.h>
#include <cxx/types.h>
#include <cxx/wasm32_wasi_toolchain.h>
#include <cxx/windows_toolchain.h>

#ifndef CXX_NO_THREADS
#include <atomic>
#endif

#include <format>
#include <iostream>

namespace cxx::lsp {

namespace {
Expand Down Expand Up @@ -74,6 +81,7 @@ struct CxxDocument::Private {
Diagnostics diagnosticsClient;
TranslationUnit unit{&diagnosticsClient};
std::shared_ptr<Toolchain> toolchain;
Vector<CompletionItem> completionItems;

#ifndef CXX_NO_THREADS
std::atomic<bool> cancelled{false};
Expand Down Expand Up @@ -202,10 +210,20 @@ void CxxDocument::cancel() {

auto CxxDocument::fileName() const -> const std::string& { return d->fileName; }

void CxxDocument::requestCodeCompletion(std::uint32_t line,
std::uint32_t column) {
void CxxDocument::codeCompletionAt(std::string source, std::uint32_t line,
std::uint32_t column,
Vector<CompletionItem> completionItems) {
std::swap(d->completionItems, completionItems);

auto& unit = d->unit;

(void)unit.blockErrors(true);

unit.preprocessor()->requestCodeCompletionAt(line, column);

parse(std::move(source));

std::swap(d->completionItems, completionItems);
}

void CxxDocument::parse(std::string source) {
Expand All @@ -227,13 +245,37 @@ void CxxDocument::parse(std::string source) {

unit.endPreprocessing();

auto stopParsingPredicate = [this] { return isCancelled(); };

auto complete = [this](const CodeCompletionContext& context) {
if (auto memberCompletionContext =
std::get_if<MemberCompletionContext>(&context)) {
// simple member completion
auto objectType = memberCompletionContext->objectType;

if (auto pointerType = type_cast<PointerType>(objectType)) {
objectType = type_cast<ClassType>(pointerType->elementType());
}

if (auto classType = type_cast<ClassType>(objectType)) {
auto classSymbol = classType->symbol();
for (auto member : classSymbol->members()) {
if (!member->name()) continue;
auto item = d->completionItems.emplace_back();
item.label(to_string(member->name()));
}
}
}
};

unit.parse(ParserConfiguration{
.checkTypes = cli.opt_fcheck,
.fuzzyTemplateResolution = true,
.staticAssert = cli.opt_fstatic_assert || cli.opt_fcheck,
.reflect = !cli.opt_fno_reflect,
.templates = cli.opt_ftemplates,
.stopParsingPredicate = [this] { return isCancelled(); },
.stopParsingPredicate = stopParsingPredicate,
.complete = complete,
});
}

Expand Down
5 changes: 3 additions & 2 deletions src/lsp/cxx/lsp/cxx_document.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,11 @@ class CxxDocument {

[[nodiscard]] auto fileName() const -> const std::string&;

void requestCodeCompletion(std::uint32_t line, std::uint32_t column);

void parse(std::string source);

void codeCompletionAt(std::string source, std::uint32_t line,
std::uint32_t column, Vector<CompletionItem> result);

[[nodiscard]] auto version() const -> long;
[[nodiscard]] auto diagnostics() const -> Vector<Diagnostic>;

Expand Down
14 changes: 8 additions & 6 deletions src/lsp/cxx/lsp/lsp_server.cc
Original file line number Diff line number Diff line change
Expand Up @@ -493,22 +493,24 @@ void Server::operator()(CompletionRequest request) {
auto column = request.params().position().character();

const auto& text = documentContents_.at(uri);
auto value = text.value;
auto source = text.value;

run([=, this, fileName = pathFromUri(uri)] {
withUnsafeJson([&](json storage) {
CompletionResponse response(storage);
response.id(request.id());

// the version is not relevant for code completion requests as we don't
// need to store the document in the cache.
auto cxxDocument = std::make_shared<CxxDocument>(cli, std::move(fileName),
/*version=*/0);

auto completionItems = response.result<Vector<CompletionItem>>();

// cxx expects 1-based line and column numbers
cxxDocument->requestCodeCompletion(line + 1, column + 1);
cxxDocument->parse(std::move(value));
cxxDocument->codeCompletionAt(std::move(source), line + 1, column + 1,
completionItems);

CompletionResponse response(storage);
response.id(request.id());
auto completionItems = response.result<Vector<CompletionItem>>();
sendToClient(response);
});
});
Expand Down
4 changes: 4 additions & 0 deletions src/parser/cxx/diagnostics_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ class DiagnosticsClient {
[[nodiscard]] auto fatalErrors() const -> bool { return fatalErrors_; }
void setFatalErrors(bool fatalErrors) { fatalErrors_ = fatalErrors; }

[[nodiscard]] auto shouldReportErrors() const -> bool {
return !blockErrors_;
}

auto blockErrors(bool blockErrors = true) -> bool {
std::swap(blockErrors_, blockErrors);
return blockErrors;
Expand Down
38 changes: 38 additions & 0 deletions src/parser/cxx/parser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,15 @@
#include <algorithm>
#include <cstring>
#include <format>
#include <iostream>
#include <ranges>
#include <unordered_set>

#include "cxx/cxx_fwd.h"
#include "cxx/parser_fwd.h"
#include "cxx/source_location.h"
#include "cxx/symbols_fwd.h"
#include "cxx/token_fwd.h"

namespace cxx {

Expand Down Expand Up @@ -1271,6 +1279,20 @@ void Parser::parse_skip_declaration(bool& skipping) {
skipping = true;
}

auto Parser::parse_completion(SourceLocation& loc) -> bool {
// if already reported a completion, return false
if (didAcceptCompletionToken_) return false;

// if there is no completer, return false
if (!config_.complete) return false;

if (!match(TokenKind::T_CODE_COMPLETION, loc)) return false;

didAcceptCompletionToken_ = true;

return true;
}

auto Parser::parse_primary_expression(ExpressionAST*& yyast,
const ExprContext& ctx) -> bool {
UnqualifiedIdAST* name = nullptr;
Expand Down Expand Up @@ -2434,6 +2456,22 @@ auto Parser::parse_member_expression(ExpressionAST*& yyast) -> bool {

ast->isTemplateIntroduced = match(TokenKind::T_TEMPLATE, ast->templateLoc);

const Type* objectType = nullptr;

if (ast->baseExpression) {
// test if the base expression has a type
objectType = ast->baseExpression->type;
}

if (SourceLocation completionLoc;
objectType && parse_completion(completionLoc)) {
// trigger the completion
config_.complete(MemberCompletionContext{
.objectType = objectType,
.accessOp = ast->accessOp,
});
}

if (!parse_unqualified_id(ast->unqualifiedId, ast->nestedNameSpecifier,
ast->isTemplateIntroduced,
/*inRequiresClause*/ false))
Expand Down
5 changes: 4 additions & 1 deletion src/parser/cxx/parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ class Parser final {
static auto prec(TokenKind tk) -> Prec;

[[nodiscard]] auto shouldStopParsing() const -> bool {
if (didAcceptCompletionToken_) return true;
if (config_.stopParsingPredicate) return config_.stopParsingPredicate();
return false;
}
Expand Down Expand Up @@ -141,6 +142,8 @@ class Parser final {

void parse_translation_unit(UnitAST*& yyast);

[[nodiscard]] auto parse_completion(SourceLocation& loc) -> bool;

[[nodiscard]] auto parse_id(const Identifier* id, SourceLocation& loc)
-> bool;
[[nodiscard]] auto parse_nospace() -> bool;
Expand Down Expand Up @@ -871,7 +874,7 @@ class Parser final {
std::uint32_t cursor_ = 0;
int templateParameterDepth_ = -1;
int templateParameterCount_ = 0;

bool didAcceptCompletionToken_ = false;
std::vector<FunctionDefinitionAST*> pendingFunctionDefinitions_;

template <typename T>
Expand Down
18 changes: 18 additions & 0 deletions src/parser/cxx/parser_fwd.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,37 @@

#pragma once

#include <cxx/symbols_fwd.h>
#include <cxx/token_fwd.h>
#include <cxx/types_fwd.h>

#include <functional>
#include <variant>

namespace cxx {

class Parser;

struct UnqualifiedCompletionContext {
Scope* scope = nullptr;
};

struct MemberCompletionContext {
const Type* objectType = nullptr;
TokenKind accessOp = TokenKind::T_DOT;
};

using CodeCompletionContext =
std::variant<UnqualifiedCompletionContext, MemberCompletionContext>;

struct ParserConfiguration {
bool checkTypes = false;
bool fuzzyTemplateResolution = false;
bool staticAssert = false;
bool reflect = true;
bool templates = false;
std::function<bool()> stopParsingPredicate;
std::function<void(const CodeCompletionContext&)> complete;
};

} // namespace cxx
24 changes: 24 additions & 0 deletions tests/unit_tests/lsp/code_completion_01.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# RUN: %cxx -lsp-test < %s | %filecheck %s

{ "method": "initialize", "id": 0 }

# CHECK: "id": 0

{ "method": "textDocument/didOpen", "id": 1, "params": { "textDocument": { "uri": "test:///source.cc", "version": 0, "text": "struct P { int x, y; }; void ff() { P p; p.\n\n\n}" } } }

{ "method": "$/setTrace", "id": 2, "params": { "value": "verbose" } }

{ "method": "textDocument/completion", "id": 3, "params": { "textDocument": { "uri": "test:///source.cc" }, "position": { "line": 2, "character": 1 } } }

# CHECK: "message": "Did receive CompletionRequest"
# CHECK: "id": 3
# CHECK: "result":
# CHECK: "label": "x"
# CHECK: "label": "y"

{ "method": "shutdown", "id": 4 }

{ "method": "exit" }