14#include <llvm/Support/FileSystem.h>
15#include <llvm/Support/Path.h>
25struct FreeVariableResult {
26 std::set<std::string> FreeVars;
35class FreeVariableCollector {
36 const nixf::VariableLookupAnalysis &VLA;
37 const nixf::Node &Root;
38 std::set<std::string> FreeVars;
39 bool HasWithVars =
false;
41 void collect(
const nixf::Node &N) {
43 if (N.
kind() == nixf::Node::NK_ExprVar) {
44 const auto &Var =
static_cast<const nixf::ExprVar &
>(N);
45 auto Result = VLA.query(Var);
53 Result.Def && !Result.Def->isBuiltin()) {
54 const nixf::Node *DefSyntax = Result.Def->syntax();
55 if (DefSyntax && !isInsideNode(DefSyntax, Root)) {
57 FreeVars.insert(std::string(Var.id().name()));
63 else if (Result.Kind ==
65 FreeVars.insert(std::string(Var.id().name()));
71 for (
const auto &Child : N.
children()) {
78 static bool isInsideNode(
const nixf::Node *N,
const nixf::Node &Root) {
82 const auto &NRange = N->
range();
83 const auto &RootRange = Root.
range();
84 return NRange.lCur().offset() >= RootRange.lCur().offset() &&
85 NRange.rCur().offset() <= RootRange.rCur().offset();
89 FreeVariableCollector(
const nixf::VariableLookupAnalysis &VLA,
90 const nixf::Node &Root)
91 : VLA(VLA), Root(Root) {}
93 FreeVariableResult collect() {
97 return {FreeVars, HasWithVars};
106std::string generateFilename(
const nixf::Node &N,
107 const nixf::ParentMapAnalysis &PM) {
109 const nixf::Node *BindingNode = PM.
upTo(N, nixf::Node::NK_Binding);
111 const auto &Binding =
static_cast<const nixf::Binding &
>(*BindingNode);
112 const auto &Names = Binding.path().names();
113 if (!Names.empty() && Names.back()->isStatic()) {
114 std::string Name = Names.back()->staticName();
116 for (
char &C : Name) {
117 if (!std::isalnum(
static_cast<unsigned char>(C)) && C !=
'-' &&
122 return Name +
".nix";
128 case nixf::Node::NK_ExprLambda:
129 return "extracted-lambda.nix";
130 case nixf::Node::NK_ExprAttrs:
131 return "extracted-attrs.nix";
132 case nixf::Node::NK_ExprList:
133 return "extracted-list.nix";
134 case nixf::Node::NK_ExprLet:
135 return "extracted-let.nix";
136 case nixf::Node::NK_ExprIf:
137 return "extracted-if.nix";
139 return "extracted.nix";
147std::string generateExtractedContent(llvm::StringRef ExprSrc,
148 const std::set<std::string> &FreeVars) {
151 if (!FreeVars.empty()) {
155 for (
const auto &Var : FreeVars) {
175std::string generateImportStatement(
const std::string &Filename,
176 const std::set<std::string> &FreeVars) {
177 std::string Import =
"import ./";
180 if (!FreeVars.empty()) {
181 Import +=
" { inherit";
182 for (
const auto &Var : FreeVars) {
196std::string stripFileScheme(llvm::StringRef
URI) {
197 if (
URI.starts_with(
"file://"))
198 return URI.drop_front(7).str();
206std::string makeUniqueFilename(llvm::StringRef Directory,
207 llvm::StringRef BaseFilename) {
208 llvm::SmallString<256> TestPath(Directory);
209 llvm::sys::path::append(TestPath, BaseFilename);
211 if (!llvm::sys::fs::exists(TestPath))
212 return std::string(BaseFilename);
215 llvm::StringRef Stem = llvm::sys::path::stem(BaseFilename);
216 llvm::StringRef Ext = llvm::sys::path::extension(BaseFilename);
219 constexpr int MaxAttempts = 100;
220 for (
int I = 1; I < MaxAttempts; ++I) {
221 std::string Candidate = (Stem +
"-" + std::to_string(I) + Ext).str();
222 TestPath = Directory;
223 llvm::sys::path::append(TestPath, Candidate);
224 if (!llvm::sys::fs::exists(TestPath))
229 return std::string(BaseFilename);
237bool isExtractable(
const nixf::Node &N) {
240 case nixf::Node::NK_ExprVar:
241 case nixf::Node::NK_ExprInt:
242 case nixf::Node::NK_ExprFloat:
243 case nixf::Node::NK_ExprString:
244 case nixf::Node::NK_ExprPath:
245 case nixf::Node::NK_ExprSPath:
248 case nixf::Node::NK_ExprSelect:
252 case nixf::Node::NK_ExprAttrs: {
253 const auto &Attrs =
static_cast<const nixf::ExprAttrs &
>(N);
255 const nixf::Binds *Binds = Attrs.binds();
256 return Binds && !Binds->
bindings().empty();
260 case nixf::Node::NK_ExprList: {
261 const auto &List =
static_cast<const nixf::ExprList &
>(N);
263 return !List.elements().empty();
267 case nixf::Node::NK_ExprLambda:
268 case nixf::Node::NK_ExprLet:
269 case nixf::Node::NK_ExprIf:
270 case nixf::Node::NK_ExprWith:
271 case nixf::Node::NK_ExprCall:
272 case nixf::Node::NK_ExprBinOp:
273 case nixf::Node::NK_ExprUnaryOp:
274 case nixf::Node::NK_ExprOpHasAttr:
275 case nixf::Node::NK_ExprAssert:
276 case nixf::Node::NK_ExprParen:
289const nixf::Node *findExtractableExpr(
const nixf::Node &N,
290 const nixf::ParentMapAnalysis &PM) {
294 if (isExtractable(N))
301 if (N.
kind() == nixf::Node::NK_Identifier ||
302 N.
kind() == nixf::Node::NK_AttrName) {
304 const nixf::Node *Parent = PM.
query(N);
305 if (Parent && Parent->
kind() == nixf::Node::NK_AttrPath) {
307 const nixf::Node *GrandParent = PM.
query(*Parent);
308 if (GrandParent && GrandParent->
kind() == nixf::Node::NK_Binding) {
309 const auto &Binding =
static_cast<const nixf::Binding &
>(*GrandParent);
310 const auto &
Value = Binding.value();
325 const std::string &FileURI, llvm::StringRef Src,
326 std::vector<lspserver::CodeAction> &Actions) {
328 const nixf::Node *ExprNode = findExtractableExpr(N, PM);
333 std::string_view ExprSrc = ExprNode->
src(Src);
338 FreeVariableCollector Collector(VLA, *ExprNode);
339 FreeVariableResult FreeVarResult = Collector.collect();
340 const std::set<std::string> &FreeVars = FreeVarResult.FreeVars;
343 std::string BaseFilename = generateFilename(*ExprNode, PM);
346 std::string SourceFilePath = stripFileScheme(FileURI);
347 llvm::SmallString<256> Directory(SourceFilePath);
348 llvm::sys::path::remove_filename(Directory);
351 std::string Filename = makeUniqueFilename(Directory, BaseFilename);
354 std::string NewFileContent = generateExtractedContent(ExprSrc, FreeVars);
357 std::string ImportStmt = generateImportStatement(Filename, FreeVars);
360 llvm::SmallString<256> NewFilePath(Directory);
361 llvm::sys::path::append(NewFilePath, Filename);
375 CreateOp.
options->overwrite =
false;
376 CreateOp.
options->ignoreIfExists =
false;
384 .newText = NewFileContent,
393 .newText = ImportStmt,
404 std::string Title =
"Extract to " + Filename;
405 if (!FreeVars.empty()) {
407 Title += std::to_string(FreeVars.size());
408 Title +=
" free variable";
409 if (FreeVars.size() > 1)
412 if (FreeVarResult.HasWithVars)
413 Title +=
", has 'with' vars";
418 Action.
title = std::move(Title);
420 Action.
edit = std::move(WE);
422 Actions.push_back(std::move(Action));
Convert between LSP and nixf types.
Shared utilities for code actions.
const std::vector< std::shared_ptr< Node > > & bindings() const
PositionRange range() const
std::string_view src(std::string_view Src) const
LexerCursorRange range() const
virtual ChildVector children() const =0
const Node * upTo(const Node &N, Node::NodeKind Kind) const
Search up until some kind of node is found.
const Node * query(const Node &N) const
void addExtractToFileAction(const nixf::Node &N, const nixf::ParentMapAnalysis &PM, const nixf::VariableLookupAnalysis &VLA, const std::string &FileURI, llvm::StringRef Src, std::vector< lspserver::CodeAction > &Actions)
Add extract-to-file action for selected expressions.
lspserver::Range toLSPRange(llvm::StringRef Code, const nixf::LexerCursorRange &R)
std::string title
A short, human-readable, title for this code action.
std::optional< std::string > kind
static const llvm::StringLiteral REFACTOR_KIND
std::optional< WorkspaceEdit > edit
The workspace edit this code action performs.
Options for CreateFile operation.
std::optional< CreateFileOptions > options
Additional options.
URIForFile uri
The resource to create.
std::vector< TextEdit > edits
VersionedTextDocumentIdentifier textDocument
The text document to change.
URIForFile uri
The text document's URI.
static URIForFile canonicalize(llvm::StringRef AbsPath, llvm::StringRef TUPath)
std::optional< std::int64_t > version
std::optional< std::vector< DocumentChange > > documentChanges