Skip to content
Open
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
9 changes: 6 additions & 3 deletions nixd/include/nixd/Controller/Controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -230,9 +230,12 @@ class Controller : public lspserver::LSPServer {

/// Determine whether or not this diagnostic is suppressed.
bool isSuppressed(nixf::Diagnostic::DiagnosticKind Kind);
void publishDiagnostics(lspserver::PathRef File,
std::optional<int64_t> Version, std::string_view Src,
const std::vector<nixf::Diagnostic> &Diagnostics);
std::vector<NixdDiagnostic> collectOptionDiagnostics(const NixTU &TU);
void
publishDiagnostics(lspserver::PathRef File, std::optional<int64_t> Version,
std::string_view Src,
const std::vector<nixf::Diagnostic> &Diagnostics,
const std::vector<NixdDiagnostic> &NixdDiagnostics = {});

void onRename(const lspserver::RenameParams &Params,
lspserver::Callback<lspserver::WorkspaceEdit> Reply);
Expand Down
25 changes: 25 additions & 0 deletions nixd/include/nixd/Controller/NixTU.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,32 @@

#include <memory>
#include <optional>
#include <string>
#include <vector>

namespace nixd {

enum class NixdDiagnosticSeverity {
Error = 1,
Warning = 2,
Information = 3,
Hint = 4,
};

struct NixdDiagnostic {
nixf::LexerCursorRange Range;
NixdDiagnosticSeverity Severity = NixdDiagnosticSeverity::Error;
std::string Code;
std::string Source;
std::string Message;
};

/// \brief Holds analyzed information about a document.
///
/// TU stands for "Translation Unit".
class NixTU {
std::vector<nixf::Diagnostic> Diagnostics;
std::vector<NixdDiagnostic> NixdDiagnostics;
std::shared_ptr<nixf::Node> AST;
std::optional<util::OwnedRegion> ASTByteCode;
std::unique_ptr<nixf::VariableLookupAnalysis> VLA;
Expand All @@ -36,6 +53,14 @@ class NixTU {
return Diagnostics;
}

[[nodiscard]] const std::vector<NixdDiagnostic> &nixdDiagnostics() const {
return NixdDiagnostics;
}

void setNixdDiagnostics(std::vector<NixdDiagnostic> Diagnostics) {
NixdDiagnostics = std::move(Diagnostics);
}

[[nodiscard]] const std::shared_ptr<nixf::Node> &ast() const { return AST; }

[[nodiscard]] const nixf::ParentMapAnalysis *parentMap() const {
Expand Down
1 change: 1 addition & 0 deletions nixd/include/nixd/Protocol/AttrSet.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ using AttrPathCompleteResponse = std::vector<std::string>;

struct OptionType {
std::optional<std::string> Description;
std::optional<std::vector<std::string>> EnumValues;
std::optional<std::string> Name;
};

Expand Down
95 changes: 88 additions & 7 deletions nixd/lib/Controller/Completion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@

#include "AST.h"
#include "CheckReturn.h"
#include "CompletionOptionEnum.h"
#include "Convert.h"

#include "lspserver/Protocol.h"
#include "lspserver/SourceCode.h"

#include "nixd/Controller/Controller.h"
#include "nixd/Protocol/AttrSet.h"
Expand All @@ -16,7 +18,10 @@

#include <boost/asio/post.hpp>

#include <llvm/ADT/StringRef.h>

#include <exception>
#include <optional>
#include <semaphore>
#include <set>
#include <utility>
Expand Down Expand Up @@ -183,14 +188,55 @@ class OptionCompletionProvider {
return Ret;
}

static std::string escapeSnippetChoice(llvm::StringRef Origin) {
std::string Ret;
for (char Ch : Origin) {
if (Ch == '\\' || Ch == ',' || Ch == '|' || Ch == '}')
Ret += "\\";
Ret += Ch;
}
return Ret;
}

static std::optional<std::string>
enumChoiceSnippet(const std::vector<std::string> &EnumValues) {
if (EnumValues.empty())
return std::nullopt;

std::string Choices;
for (std::size_t I = 0; I < EnumValues.size(); ++I) {
if (I != 0)
Choices += ",";
Choices += escapeSnippetChoice(quoteNixString(EnumValues[I]));
}
return "${1|" + Choices + "|}";
}

static std::optional<std::string>
defaultEnumValue(const OptionDescription &Desc) {
if (!Desc.Type || !Desc.Type->EnumValues || Desc.Type->EnumValues->empty())
return std::nullopt;
return quoteNixString(Desc.Type->EnumValues->front());
}

void fillInsertText(CompletionItem &Item, const std::string &Name,
const OptionDescription &Desc) const {
std::optional<std::string> EnumDefault = defaultEnumValue(Desc);
if (!ClientSupportSnippet) {
Item.insertTextFormat = InsertTextFormat::PlainText;
Item.insertText = Name + " = " + Desc.Example.value_or("") + ";";
Item.insertText =
Name + " = " + EnumDefault.value_or(Desc.Example.value_or("")) + ";";
return;
}
Item.insertTextFormat = InsertTextFormat::Snippet;
if (Desc.Type && Desc.Type->EnumValues) {
if (std::optional<std::string> Choice =
enumChoiceSnippet(*Desc.Type->EnumValues)) {
Item.insertText = Name + " = " + *Choice + ";";
return;
}
}

Item.insertText =
Name + " = " +
"${1:" + escapeCharacters({'\\', '$', '}'}, Desc.Example.value_or("")) +
Expand Down Expand Up @@ -245,6 +291,9 @@ class OptionCompletionProvider {
Item.detail += "? (missing type)";
}
addItem(Items, std::move(Item));
addOptionEnumNameItems(
Field, Desc, ModuleOrigin,
[&Items](CompletionItem Item) { addItem(Items, std::move(Item)); });
} else {
Item.kind = OptionAttrKind;
addItem(Items, std::move(Item));
Expand Down Expand Up @@ -378,27 +427,46 @@ void Controller::onCompletion(const CompletionParams &Params,
Callback<CompletionList> Reply) {
using CheckTy = CompletionList;
auto Action = [Reply = std::move(Reply), URI = Params.textDocument.uri,
Pos = toNixfPosition(Params.position), this]() mutable {
Pos = toNixfPosition(Params.position),
LSPPos = Params.position, this]() mutable {
const auto File = URI.file().str();
return Reply([&]() -> llvm::Expected<CompletionList> {
const auto TU = CheckDefault(getTU(File));
const auto AST = CheckDefault(getAST(*TU));
llvm::Expected<size_t> MaybeOffset =
lspserver::positionToOffset(TU->src(), LSPPos);
std::optional<std::size_t> Offset;
if (MaybeOffset)
Offset = *MaybeOffset;
else
llvm::consumeError(MaybeOffset.takeError());

const auto *Desc = AST->descend({Pos, Pos});
CheckDefault(Desc && Desc->children().empty());
CheckDefault(Desc);

const auto &N = *Desc;
const auto &PM = *TU->parentMap();
const auto &UpExpr = *CheckDefault(PM.upExpr(N));
const auto *UpExpr = Desc->children().empty() ? PM.upExpr(N) : nullptr;

return [&]() {
CompletionList List;
const VariableLookupAnalysis &VLA = *TU->variableLookup();
try {
switch (UpExpr.kind()) {
if (!UpExpr) {
if (List.items.empty() && Offset)
completeOptionEnumValuesAfterEq(
TU->src(), *Offset, OptionsLock, Options,
[&List](CompletionItem Item) {
addItem(List.items, std::move(Item));
});
return List;
}

switch (UpExpr->kind()) {
// In these cases, assume the cursor have "variable" scoping.
case Node::NK_ExprVar: {
completeVarName(VLA, PM, static_cast<const nixf::ExprVar &>(UpExpr),
completeVarName(VLA, PM,
static_cast<const nixf::ExprVar &>(*UpExpr),
*nixpkgsClient(), List.items);
return List;
}
Expand All @@ -407,14 +475,27 @@ void Controller::onCompletion(const CompletionParams &Params,
// foo.|
// foo.a.bar|
case Node::NK_ExprSelect: {
const auto &Select = static_cast<const nixf::ExprSelect &>(UpExpr);
const auto &Select = static_cast<const nixf::ExprSelect &>(*UpExpr);
completeSelect(Select, *nixpkgsClient(), VLA, PM,
N.kind() == Node::NK_Dot, List.items);
return List;
}
case Node::NK_ExprAttrs: {
completeAttrPath(N, PM, OptionsLock, Options,
ClientCaps.CompletionSnippets, List.items);
if (List.items.empty() && Offset)
completeOptionEnumValuesAfterEq(
TU->src(), *Offset, OptionsLock, Options,
[&List](CompletionItem Item) {
addItem(List.items, std::move(Item));
});
return List;
}
case Node::NK_ExprString: {
completeOptionEnumValuesInString(
N, PM, OptionsLock, Options, [&List](CompletionItem Item) {
addItem(List.items, std::move(Item));
});
return List;
}
default:
Expand Down
51 changes: 51 additions & 0 deletions nixd/lib/Controller/CompletionOptionEnum.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#pragma once

#include "lspserver/Protocol.h"

#include "nixd/Controller/Controller.h"
#include "nixd/Protocol/AttrSet.h"

#include <llvm/ADT/StringRef.h>

#include <functional>
#include <mutex>
#include <optional>
#include <string>
#include <vector>

namespace nixf {
class Node;
class ParentMapAnalysis;
} // namespace nixf

namespace nixd {

using AddCompletionItem = std::function<void(lspserver::CompletionItem)>;

std::string quoteNixString(llvm::StringRef Origin);

std::optional<OptionDescription>
resolveOptionInfo(AttrSetClient &Client, const std::vector<std::string> &Scope);

void completeOptionEnumValuesForScope(
const std::vector<std::string> &Scope, std::mutex &OptionsLock,
Controller::OptionMapTy &Options, const AddCompletionItem &AddItem,
bool InsertQuotedValue, std::optional<lspserver::Range> Range = {});

void addOptionEnumNameItems(const OptionField &Field,
const OptionDescription &Desc,
const std::string &ModuleOrigin,
const AddCompletionItem &AddItem);

void completeOptionEnumValuesInString(const nixf::Node &N,
const nixf::ParentMapAnalysis &PM,
std::mutex &OptionsLock,
Controller::OptionMapTy &Options,
const AddCompletionItem &AddItem);

void completeOptionEnumValuesAfterEq(llvm::StringRef Src, std::size_t Offset,
std::mutex &OptionsLock,
Controller::OptionMapTy &Options,
const AddCompletionItem &AddItem);

} // namespace nixd
Loading
Loading