From 6fbd189ae1f0f74aa2449990ae58a44b8bf73812 Mon Sep 17 00:00:00 2001 From: ylh Date: Mon, 26 Aug 2024 06:00:37 -0700 Subject: [PATCH] completeFlakeRefWithFragment: handle names with dots --- src/libcmd/installables.cc | 30 +++++++++++++++++++++++++----- tests/functional/completions.sh | 12 ++++++++++++ 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index 0fe956ec0..07aaf4caa 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -359,13 +359,28 @@ void completeFlakeRefWithFragment( for (auto & attrPathPrefixS : attrPathPrefixes) { auto attrPathPrefix = parseAttrPath(*evalState, attrPathPrefixS); - auto attrPathS = attrPathPrefixS + std::string(fragment); - auto attrPath = parseAttrPath(*evalState, attrPathS); + // we receive backslashes as typed; left alone, they get into attr names. + auto fragmentUnshell = replaceStrings(std::string(fragment), "\\\"", "\""); + auto attrPathS = attrPathPrefixS + fragmentUnshell; + auto incompleteQuote = false; + + std::vector attrPath; + try { + attrPath = parseAttrPath(*evalState, attrPathS); + } catch (const ParseError&) { + attrPath = parseAttrPath(*evalState, attrPathS + "\""); + incompleteQuote = true; + } std::string lastAttr; - if (!attrPath.empty() && !hasSuffix(attrPathS, ".")) { + while (!attrPath.empty() && !hasSuffix(attrPathS, ".")) { lastAttr = evalState->symbols[attrPath.back()]; attrPath.pop_back(); + if (incompleteQuote && lastAttr.empty()) { + incompleteQuote = false; + continue; + } + break; } auto attr = root->findAlongAttrPath(attrPath); @@ -376,8 +391,13 @@ void completeFlakeRefWithFragment( auto attrPath2 = (*attr)->getAttrPath(attr2); /* Strip the attrpath prefix. */ attrPath2.erase(attrPath2.begin(), attrPath2.begin() + attrPathPrefix.size()); - // FIXME: handle names with dots - completions.add(flakeRefS + "#" + prefixRoot + concatStringsSep(".", evalState->symbols.resolve(attrPath2))); + Strings escs; + for (auto sym : evalState->symbols.resolve(attrPath2)) { + std::stringstream ss; + ss << sym; + escs.push_back(replaceStrings(ss.str(), "\"", "\\\"")); + } + completions.add(flakeRefS + "#" + prefixRoot + concatStringsSep(".", escs)); } } } diff --git a/tests/functional/completions.sh b/tests/functional/completions.sh index 9164c5013..3d82acddf 100755 --- a/tests/functional/completions.sh +++ b/tests/functional/completions.sh @@ -34,6 +34,16 @@ mkdir -p err cat < err/flake.nix throw "error" EOF +mkdir -p hasdots +cat < hasdots/flake.nix +{ + outputs = i: { + "sample.dotted.output" = { + subAttr = 1; + }; + }; +} +EOF # Test the completion of a subcommand [[ "$(NIX_GET_COMPLETIONS=1 nix buil)" == $'normal\nbuild\t' ]] @@ -73,3 +83,5 @@ NIX_GET_COMPLETIONS=3 nix build --option allow-import-from | grep -- "allow-impo [[ "$(NIX_GET_COMPLETIONS=4 nix eval --file ./foo/flake.nix outp)" == $'attrs\noutputs\t' ]] [[ "$(NIX_GET_COMPLETIONS=4 nix eval --file ./err/flake.nix outp 2>&1)" == $'attrs' ]] [[ "$(NIX_GET_COMPLETIONS=2 nix eval ./err\# 2>&1)" == $'attrs' ]] +[[ "$(NIX_GET_COMPLETIONS=2 nix eval './hasdots#s')" == $'attrs\n./hasdots#\\"sample.dotted.output\\"\t' ]] +[[ "$(NIX_GET_COMPLETIONS=2 nix eval './hasdots#\"sample.dotted.output\".s')" == $'attrs\n./hasdots#\\"sample.dotted.output\\".subAttr\t' ]]