jli  Linuxx86_641.10.3v1.10.30b4590a5507d3f3046e5bafc007cacbbfc9b310bqaPlutoDependencyExploreratluskerkikkerkrBm97CP/opt/julia/packages/PlutoDependencyExplorer/AB0rJ/src/PlutoDependencyExplorer.jljA#Climate Justice!ExpressionExplorerH/opt/julia/packages/PlutoDependencyExplorer/AB0rJ/src/data structures.jljAK/opt/julia/packages/PlutoDependencyExplorer/AB0rJ/src/ExpressionExplorer.jljA#Climate Justice!ExpressionExplorerExpressionExplorerExtrasz`sZPn7MarkdownExpressionExplorerExtrasFake PlutoRunner!@'Z萠WL ~InteractiveUtilsExpressionExplorerExtrasFake PlutoRunnerA/opt/julia/packages/PlutoDependencyExplorer/AB0rJ/src/Topology.jljA?/opt/julia/packages/PlutoDependencyExplorer/AB0rJ/src/Errors.jljAI/opt/julia/packages/PlutoDependencyExplorer/AB0rJ/src/TopologicalOrder.jljAJ/opt/julia/packages/PlutoDependencyExplorer/AB0rJ/src/topological_order.jljAG/opt/julia/packages/PlutoDependencyExplorer/AB0rJ/src/TopologyUpdate.jljA CoremуJ5Basemу]J5MainmуJ5ArgToolsBń x(mуF K5 Artifactsmr-V3|mу K5Base64UlD*_mу> K5CRC32c\y.jmуj K5 FileWatchingXzsy`{,zmуh& K5LibdluVW59˗,mу-" K5LoggingT{VhUXM=mуrU" K5MmapP~:xg,Omу|' K5NetworkOptionsC0YW,mуʠ, K5SHAQ<$!<%mу1 K5 Serialization [)*k1mу-G K5Sockets1V$ bdސݗmуYBY K5UnicodeP>I>Nrmуeszo K5 LinearAlgebraSm7̏mуuux K5 OpenBLAS_jll[(Śb6EcQ FmуDux K5libblastrampoline_jllLSۆ }lxӠmу^} K5MarkdownZPn7z`smу/Ed~ K5Printfg^cX׸QDmу;h K5Random_ɢ?\Ymу? K5TarOi>աmу!t, K5DatesEY8pj2 mуX K5FuturebS;3{I xVMmуsD K5InteractiveUtilsWL ~@'ZmуVg K5LibGit2Z[&RPTv3EКRmу8J K5 LibGit2_jll YXg}]$mуD K5 MbedTLS_jllAX 3ȡ_mу- K5 LibSSH2_jlloTZk)߆ "one", 2 => "two", 3 => "three"), [1, 3]) # result: `Dict(2 => "two")` ``` """ setdiffkeys(d::Dict{K,V}, key_itrs...) where {K,V} = Dict{K,V}(k => d[k] for k in setdiff(keys(d), key_itrs...)) setdiffkeys(d::ImmutableDefaultDict{K,V}, key_itrs...) where {K,V} = ImmutableDefaultDict{K,V}(d.default, setdiffkeys(d.container, key_itrs...)) K/opt/julia/packages/PlutoDependencyExplorer/AB0rJ/src/ExpressionExplorer.jl#using ExpressionExplorer @deprecate ReactiveNode_from_expr(args...; kwargs...) ExpressionExplorer.compute_reactive_node(args...; kwargs...) module ExpressionExplorerExtras import ..PlutoDependencyExplorer using ExpressionExplorer using ExpressionExplorer: ScopeState module Fake module PlutoRunner using Markdown using InteractiveUtils macro bind(def, element) quote global $(esc(def)) = element end end end import .PlutoRunner end import .Fake """ ExpressionExplorer does not explore inside macro calls, i.e. the arguments of a macrocall (like `a+b` in `@time a+b`) are ignored. Normally, you would macroexpand an expression before giving it to ExpressionExplorer, but in Pluto we sometimes need to explore expressions *before* executing code. In those cases, we want most accurate result possible. Our extra needs are: 1. Macros included in Julia base, Markdown and `@bind` can be expanded statically. (See `maybe_macroexpand_pluto`.) 2. If a macrocall argument contains a "special heuristic" like `Pkg.activate()` or `using Something`, we need to surface this to be visible to ExpressionExplorer and Pluto. We do this by placing the macrocall in a block, and copying the argument after to the macrocall. 3. If a macrocall argument contains other macrocalls, we need these nested macrocalls to be visible. We do this by placing the macrocall in a block, and creating new macrocall expressions with the nested macrocall names, but without arguments. """ function pretransform_pluto(ex) if Meta.isexpr(ex, :macrocall) to_add = Expr[] maybe_expanded = maybe_macroexpand_pluto(ex) if maybe_expanded === ex # we were not able to expand statically for arg in ex.args[begin+1:end] arg_transformed = pretransform_pluto(arg) macro_arg_symstate = ExpressionExplorer.compute_symbols_state(arg_transformed) # When this macro has something special inside like `Pkg.activate()`, we're going to make sure that ExpressionExplorer treats it as normal code, not inside a macrocall. (so these heuristics trigger later) if arg isa Expr && macro_has_special_heuristic_inside(symstate = macro_arg_symstate, expr = arg_transformed) # then the whole argument expression should be added push!(to_add, arg_transformed) else for fn in macro_arg_symstate.macrocalls push!(to_add, Expr(:macrocall, fn)) # fn is a FunctionName # normally this would not be a legal expression, but ExpressionExplorer handles it correctly so it's all cool end end end Expr( :block, # the original expression, not expanded. ExpressionExplorer will just explore the name of the macro, and nothing else. ex, # any expressions that we need to sneakily add to_add... ) else Expr( :block, # We were able to expand the macro, so let's recurse on the result. pretransform_pluto(maybe_expanded), # the name of the macro that got expanded Expr(:macrocall, ex.args[1]), ) end elseif Meta.isexpr(ex, :module) ex elseif ex isa Expr # recurse Expr(ex.head, (pretransform_pluto(a) for a in ex.args)...) else ex end end """ Uses `cell_precedence_heuristic` to determine if we need to include the contents of this macro in the symstate. This helps with things like a Pkg.activate() that's in a macro, so Pluto still understands to disable nbpkg. """ function macro_has_special_heuristic_inside(; symstate::SymbolsState, expr::Expr)::Bool # Also, because I'm lazy and don't want to copy any code, imma use cell_precedence_heuristic here. # Sad part is, that this will also include other symbols used in this macro... but come'on node = ReactiveNode(symstate) code = PlutoDependencyExplorer.ExprAnalysisCache( parsedcode = expr, module_usings_imports = ExpressionExplorer.compute_usings_imports(expr), ) return PlutoDependencyExplorer.cell_precedence_heuristic(node, code) < PlutoDependencyExplorer.DEFAULT_PRECEDENCE_HEURISTIC end const can_macroexpand_no_bind = Set(Symbol.(["@md_str", "Markdown.@md_str", "@gensym", "Base.@gensym", "@enum", "Base.@enum", "@assert", "Base.@assert", "@cmd"])) const can_macroexpand = can_macroexpand_no_bind ∪ Set(Symbol.(["@bind", "PlutoRunner.@bind"])) const plutorunner_id = Base.PkgId(Base.UUID("dc6b355a-2368-4481-ae6d-ae0351418d79"), "PlutoRunner") const pluto_id = Base.PkgId(Base.UUID("c3e4b0f8-55cb-11ea-2926-15256bba5781"), "Pluto") const found_plutorunner = Ref{Union{Nothing,Module}}(nothing) """ Find the module `PlutoRunner`, if it is currently loaded. We use `PlutoRunner` to macroexpand `@bind`. If not found, the fallback is `Fake.PlutoRunner`. """ function get_plutorunner() fpr = found_plutorunner[] if fpr === nothing # lets try really hard to find it! if haskey(Base.loaded_modules, pluto_id) found_plutorunner[] = Base.loaded_modules[pluto_id].PlutoRunner elseif haskey(Base.loaded_modules, plutorunner_id) found_plutorunner[] = Base.loaded_modules[plutorunner_id] elseif isdefined(Main, :PlutoRunner) && Main.PlutoRunner isa Module found_plutorunner[] = Main.PlutoRunner else # not found Fake.PlutoRunner end else fpr end end """ If the macro is **known to Pluto**, expand or 'mock expand' it, if not, return the expression. Macros from external packages are not expanded, this is done later in the pipeline. See https://github.com/fonsp/Pluto.jl/pull/1032 """ function maybe_macroexpand_pluto(ex::Expr; recursive::Bool=false, expand_bind::Bool=true) result::Expr = if ex.head === :macrocall funcname = ExpressionExplorer.split_funcname(ex.args[1]) if funcname.joined ∈ (expand_bind ? can_macroexpand : can_macroexpand_no_bind) try macroexpand(get_plutorunner(), ex; recursive=false)::Expr catch e @debug "Could not macroexpand" ex exception=(e, catch_backtrace()) ex end else ex end else ex end if recursive # Not using broadcasting because that is expensive compilation-wise for `result.args::Any`. expanded = Any[] for arg in result.args ex = maybe_macroexpand_pluto(arg; recursive, expand_bind) push!(expanded, ex) end return Expr(result.head, expanded...) else return result end end maybe_macroexpand_pluto(ex::Any; kwargs...) = ex ############### function collect_implicit_usings(usings_imports::ExpressionExplorer.UsingsImports) implicit_usings = Set{Expr}() for (using_, isglobal) in zip(usings_imports.usings, usings_imports.usings_isglobal) if !(isglobal && is_implicit_using(using_)) continue end for arg in using_.args push!(implicit_usings, transform_dot_notation(arg)) end end implicit_usings end is_implicit_using(ex::Expr) = Meta.isexpr(ex, :using) && length(ex.args) >= 1 && !Meta.isexpr(ex.args[1], :(:)) function transform_dot_notation(ex::Expr) if Meta.isexpr(ex, :(.)) Expr(:block, ex.args[end]) else ex end end ############### """ ```julia can_be_function_wrapped(ex)::Bool ``` Is this code simple enough that we can wrap it inside a function, and run the function in global scope instead of running the code directly? Look for `Pluto.PlutoRunner.Computer` to learn more. """ function can_be_function_wrapped(x::Expr) if x.head === :global || # better safe than sorry x.head === :using || x.head === :import || x.head === :export || x.head === :public || # Julia 1.11 x.head === :module || x.head === :incomplete || # Only bail on named functions, but anonymous functions (args[1].head == :tuple) are fine. # TODO Named functions INSIDE other functions should be fine too (x.head === :function && !Meta.isexpr(x.args[1], :tuple)) || x.head === :macro || # Cells containing macrocalls will actually be function wrapped using the expanded version of the expression # See https://github.com/fonsp/Pluto.jl/pull/1597 x.head === :macrocall || x.head === :struct || x.head === :abstract || (x.head === :(=) && ExpressionExplorer.is_function_assignment(x)) || # f(x) = ... (x.head === :call && (x.args[1] === :eval || x.args[1] === :include)) false else all(can_be_function_wrapped, x.args) end end can_be_function_wrapped(x::Any) = true end A/opt/julia/packages/PlutoDependencyExplorer/AB0rJ/src/Topology.jlimport ExpressionExplorer: UsingsImports, SymbolsState "A container for the result of parsing the cell code, with some extra metadata." Base.@kwdef struct ExprAnalysisCache code::String="" parsedcode::Expr=Expr(:toplevel, LineNumberNode(1), Expr(:block)) module_usings_imports::UsingsImports = UsingsImports() function_wrapped::Bool=false forced_expr_id::Union{UInt,Nothing}=nothing end function ExprAnalysisCache(code_str::String, parsedcode::Expr) ExprAnalysisCache(; code=code_str, parsedcode, module_usings_imports=ExpressionExplorer.compute_usings_imports(parsedcode), function_wrapped=ExpressionExplorerExtras.can_be_function_wrapped(parsedcode), ) end function ExprAnalysisCache(old_cache::ExprAnalysisCache; new_properties...) properties = Dict{Symbol,Any}(field => getproperty(old_cache, field) for field in fieldnames(ExprAnalysisCache)) merge!(properties, Dict{Symbol,Any}(new_properties)) ExprAnalysisCache(;properties...) end """ The (information needed to create the) dependency graph of a notebook. Cells are linked by the names of globals that they define and reference. 🕸 `NotebookTopology` is an immutable structure. In Pluto's case, where the notebook is constantly changing (being edited), it functions as a *snapshot* of the notebook's reactive state at a current time. This also means that the `NotebookTopology` cannot be mutated to reflect changes in the notebook. This is done by the `update_topology` function, which takes an old topology and calculates the next one. # Fields - `nodes` is really the **dependency graph**. For each cell, it stores the dependency links. - `codes` is a snapshot of the cell codes at the time when the `topology` was calculated, including some metadata that is used by Pluto. - `cell_order` is a snapshot of the cell order at the time when the `topology` was calculated. - `unresolved_cells` contains cells that still have unresolved macro calls - `disabled_cells` contains cells that are disabled (used by Pluto) """ Base.@kwdef struct NotebookTopology{C <: AbstractCell} nodes::ImmutableDefaultDict{C,ReactiveNode}=ImmutableDefaultDict{C,ReactiveNode}(ReactiveNode) codes::ImmutableDefaultDict{C,ExprAnalysisCache}=ImmutableDefaultDict{C,ExprAnalysisCache}(ExprAnalysisCache) cell_order::ImmutableVector{C}=ImmutableVector{C}() unresolved_cells::ImmutableSet{C} = ImmutableSet{C}() disabled_cells::ImmutableSet{C} = ImmutableSet{C}() end # BIG TODO HERE: CELL ORDER all_cells(topology::NotebookTopology) = topology.cell_order.c is_resolved(topology::NotebookTopology) = isempty(topology.unresolved_cells) is_resolved(topology::NotebookTopology, c::AbstractCell) = c in topology.unresolved_cells is_disabled(topology::NotebookTopology, c::AbstractCell) = c in topology.disabled_cells function set_unresolved(topology::NotebookTopology{C}, unresolved_cells::Vector{C}) where C <: AbstractCell codes = Dict{C,ExprAnalysisCache}( cell => ExprAnalysisCache(topology.codes[cell]; function_wrapped=false, forced_expr_id=nothing) for cell in unresolved_cells ) NotebookTopology{C}( nodes=topology.nodes, codes=merge(topology.codes, codes), unresolved_cells=union(topology.unresolved_cells, unresolved_cells), cell_order=topology.cell_order, disabled_cells=topology.disabled_cells, ) end """ exclude_roots(topology::NotebookTopology, roots_to_exclude)::NotebookTopology Returns a new topology as if `topology` was created with all code for `roots_to_exclude` being empty, preserving disabled cells and cell order. """ function exclude_roots(topology::NotebookTopology{C}, cells::Vector{C}) where C <: AbstractCell NotebookTopology{C}( nodes=setdiffkeys(topology.nodes, cells), codes=setdiffkeys(topology.codes, cells), unresolved_cells=ImmutableSet{C}(setdiff(topology.unresolved_cells.c, cells); skip_copy=true), cell_order=topology.cell_order, disabled_cells=topology.disabled_cells, ) end ?/opt/julia/packages/PlutoDependencyExplorer/AB0rJ/src/Errors.jlimport Base: showerror import ExpressionExplorer: FunctionName abstract type ReactivityError <: Exception end struct CyclicReferenceError <: ReactivityError syms::Set{Symbol} end function CyclicReferenceError(topology::NotebookTopology, cycle::AbstractVector{<:AbstractCell}) CyclicReferenceError(cyclic_variables(topology, cycle)) end struct MultipleDefinitionsError <: ReactivityError syms::Set{Symbol} end function MultipleDefinitionsError(topology::NotebookTopology, cell::AbstractCell, all_definers) competitors = setdiff(all_definers, [cell]) defs(c) = topology.nodes[c].funcdefs_without_signatures ∪ topology.nodes[c].definitions MultipleDefinitionsError( union((defs(cell) ∩ defs(c) for c in competitors)...) ) end const _hint1 = "Combine all definitions into a single reactive cell using a `begin ... end` block." # TODO: handle case when cells are in cycle, but variables aren't function showerror(io::IO, cre::CyclicReferenceError) print(io, "Cyclic references among ") println(io, join(cre.syms, ", ", " and ")) print(io, _hint1) end function showerror(io::IO, mde::MultipleDefinitionsError) print(io, "Multiple definitions for ") println(io, join(mde.syms, ", ", " and ")) print(io, _hint1) # TODO: hint about mutable globals end I/opt/julia/packages/PlutoDependencyExplorer/AB0rJ/src/TopologicalOrder.jlimport ExpressionExplorer: SymbolsState, FunctionName "Information container about the cells to run in a reactive call and any cells that will err." Base.@kwdef struct TopologicalOrder{C <: AbstractCell} input_topology::NotebookTopology "Cells that form a directed acyclic graph, in topological order." runnable::Vector{C} "Cells that are in a directed cycle, with corresponding `ReactivityError`s." errable::Dict{C,ReactivityError} end J/opt/julia/packages/PlutoDependencyExplorer/AB0rJ/src/topological_order.jl&abstract type ChildExplorationResult end struct Ok <: ChildExplorationResult end struct Cycle{C <: AbstractCell} <: ChildExplorationResult cycled_cells::Vector{C} end """ Return a `TopologicalOrder` that lists the cells to be evaluated in a single reactive run, in topological order. Includes the given roots. # Keyword arguments - `allow_multiple_defs::Bool = false` If `false` (default), multiple definitions are not allowed. When a cell is found that defines a variable that is also defined by another cell (this other cell is called a *fellow assigner*), then both cells are marked as `errable` and not `runnable`. If `true`, then multiple definitions are allowed, in the sense that we ignore the existance of other cells that define the same variable. - `skip_at_partial_multiple_defs::Bool = false` If `true` (not default), and `allow_multiple_defs = true` (not default), then the search stops going downward when finding a cell that has fellow assigners, *unless all fellow assigners can be reached by the `roots`*, in which case we continue searching downward. In other words, if there is a set of fellow assigners that can only be reached **partially** by the roots, then this set blocks the search, and cells that depend on the set are not found. """ function topological_order(topology::NotebookTopology{C}, roots::AbstractVector{C}; allow_multiple_defs::Bool=false, skip_at_partial_multiple_defs::Bool=false, )::TopologicalOrder{C} where C <: AbstractCell if skip_at_partial_multiple_defs @assert allow_multiple_defs end entries = C[] exits = C[] errable = Dict{C,ReactivityError}() # https://xkcd.com/2407/ function bfs(cell::C)::ChildExplorationResult if cell in exits return Ok() elseif haskey(errable, cell) return Ok() elseif length(entries) > 0 && entries[end] === cell return Ok() # a cell referencing itself is legal elseif cell in entries currently_in = setdiff(entries, exits) cycle = currently_in[findfirst(isequal(cell), currently_in):end] if !cycle_is_among_functions(topology, cycle) for cell in cycle errable[cell] = CyclicReferenceError(topology, cycle) end return Cycle(cycle) end return Ok() end # used for cleanups of wrong cycles current_entries_num = length(entries) current_exits_num = length(exits) push!(entries, cell) assigners = where_assigned(topology, cell) referencers = where_referenced(topology, cell) |> Iterators.reverse if !allow_multiple_defs && length(assigners) > 1 for c in assigners errable[c] = MultipleDefinitionsError(topology, c, assigners) end end should_continue_search_down = !skip_at_partial_multiple_defs || all(c -> c === cell || c ∈ exits, assigners) should_search_fellow_assigners_if_any = !allow_multiple_defs to_search_next = if should_continue_search_down if should_search_fellow_assigners_if_any union(assigners, referencers) else referencers end else C[] end for c in to_search_next if c !== cell child_result = bfs(c) # No cycle for this child or the cycle has no soft edges if child_result isa Ok || cell ∉ child_result.cycled_cells continue end # Can we cleanup the cycle from here or is it caused by a parent cell? # if the edge to the child cell is composed of soft assigments only then we can try to "break" # it else we bubble the result up to the parent until it is # either out of the cycle or a soft-edge is found if !is_soft_edge(topology, cell, c) # Cleanup all entries & child exits deleteat!(entries, current_entries_num+1:length(entries)) deleteat!(exits, current_exits_num+1:length(exits)) return child_result end # Cancel exploring this child (c) # 1. Cleanup the errables for cycled_cell in child_result.cycled_cells delete!(errable, cycled_cell) end # 2. Remove the current child (c) from the entries if it was just added if entries[end] === c pop!(entries) end continue # the cycle was created by us so we can keep exploring other children end end push!(exits, cell) Ok() end # we first move cells to the front if they call `import` or `using` # we use MergeSort because it is a stable sort: leaves cells in order if they are in the same category prelim_order_1 = sort(roots, alg=MergeSort, by=c -> cell_precedence_heuristic(topology, c)) # reversing because our search returns reversed order for i in length(prelim_order_1):-1:1 bfs(prelim_order_1[i]) end ordered = reverse(exits) TopologicalOrder(topology, setdiff(ordered, keys(errable)), errable) end topological_order(topology::NotebookTopology; kwargs...) = topological_order(topology, all_cells(topology); kwargs...) Base.collect(notebook_topo_order::TopologicalOrder) = union(notebook_topo_order.runnable, keys(notebook_topo_order.errable)) function disjoint(a, b) !any(x in a for x in b) end "Return the cells that reference any of the symbols defined by the given cell. Non-recursive: only direct dependencies are found." function where_referenced(topology::NotebookTopology{C}, myself::C)::Vector{C} where C <: AbstractCell to_compare = union(topology.nodes[myself].definitions, topology.nodes[myself].soft_definitions, topology.nodes[myself].funcdefs_without_signatures) where_referenced(topology, to_compare) end "Return the cells that reference any of the given symbols. Non-recursive: only direct dependencies are found." function where_referenced(topology::NotebookTopology{C}, to_compare::Set{Symbol})::Vector{C} where C <: AbstractCell return filter(all_cells(topology)) do cell !disjoint(to_compare, topology.nodes[cell].references) end end "Returns whether or not the edge between two cells is composed only of \"soft\"-definitions" function is_soft_edge(topology::NotebookTopology{C}, parent_cell::C, child_cell::C) where C <: AbstractCell hard_definitions = union(topology.nodes[parent_cell].definitions, topology.nodes[parent_cell].funcdefs_without_signatures) soft_definitions = topology.nodes[parent_cell].soft_definitions child_references = topology.nodes[child_cell].references disjoint(hard_definitions, child_references) && !disjoint(soft_definitions, child_references) end "Return the cells that also assign to any variable or method defined by the given cell. If more than one cell is returned (besides the given cell), then all of them should throw a `MultipleDefinitionsError`. Non-recursive: only direct dependencies are found." function where_assigned(topology::NotebookTopology{C}, myself::C)::Vector{C} where C where_assigned(topology, topology.nodes[myself]) end function where_assigned(topology::NotebookTopology{C}, self::ReactiveNode)::Vector{C} where C return filter(all_cells(topology)) do cell other = topology.nodes[cell] !( disjoint(self.definitions, other.definitions) && disjoint(self.definitions, other.funcdefs_without_signatures) && disjoint(self.funcdefs_without_signatures, other.definitions) && disjoint(self.funcdefs_with_signatures, other.funcdefs_with_signatures) ) end end function where_assigned(topology::NotebookTopology{C}, to_compare::Set{Symbol})::Vector{C} where C filter(all_cells(topology)) do cell other = topology.nodes[cell] !( disjoint(to_compare, other.definitions) && disjoint(to_compare, other.funcdefs_without_signatures) ) end end function cyclic_variables(topology::NotebookTopology, cycle)::Set{Symbol} referenced_during_cycle = union!(Set{Symbol}(), (topology.nodes[c].references for c in cycle)...) assigned_during_cycle = union!(Set{Symbol}(), (topology.nodes[c].definitions ∪ topology.nodes[c].soft_definitions ∪ topology.nodes[c].funcdefs_without_signatures for c in cycle)...) referenced_during_cycle ∩ assigned_during_cycle end function cycle_is_among_functions(topology::NotebookTopology, cycle)::Bool cyclics = cyclic_variables(topology, cycle) all( any(s ∈ topology.nodes[c].funcdefs_without_signatures for c in cycle) for s in cyclics ) end function cell_precedence_heuristic(topology::NotebookTopology{C}, cell::C) where C <: AbstractCell cell_precedence_heuristic(topology.nodes[cell], topology.codes[cell]) end """Assigns a number to a cell - cells with a lower number might run first. This is used to treat reactive dependencies between cells that cannot be found using static code anylsis.""" function cell_precedence_heuristic(node::ReactiveNode, code::ExprAnalysisCache)::Real if :Pkg ∈ node.definitions 1 elseif :DrWatson ∈ node.definitions 2 elseif Symbol("Pkg.API.activate") ∈ node.references || Symbol("Pkg.activate") ∈ node.references || Symbol("@pkg_str") ∈ node.references || # https://juliadynamics.github.io/DrWatson.jl/dev/project/#DrWatson.quickactivate Symbol("quickactivate") ∈ node.references || Symbol("@quickactivate") ∈ node.references || Symbol("DrWatson.@quickactivate") ∈ node.references || Symbol("DrWatson.quickactivate") ∈ node.references 3 elseif Symbol("Pkg.API.add") ∈ node.references || Symbol("Pkg.add") ∈ node.references || Symbol("Pkg.API.develop") ∈ node.references || Symbol("Pkg.develop") ∈ node.references 4 elseif :LOAD_PATH ∈ node.references # https://github.com/fonsp/Pluto.jl/issues/323 5 elseif :Revise ∈ node.definitions # Load Revise before other packages so that it can properly `revise` them. 6 elseif !isempty(code.module_usings_imports.usings) # always do `using X` before other cells, because we don't (yet) know which cells depend on it (we only know it with `import X` and `import X: y, z`) 7 elseif :include ∈ node.references # https://github.com/fonsp/Pluto.jl/issues/193 # because we don't (yet) know which cells depend on it 8 else DEFAULT_PRECEDENCE_HEURISTIC end end const DEFAULT_PRECEDENCE_HEURISTIC = 9 G/opt/julia/packages/PlutoDependencyExplorer/AB0rJ/src/TopologyUpdate.jl*import ExpressionExplorer import .ExpressionExplorerExtras import ExpressionExplorer: SymbolsState, FunctionNameSignaturePair """ ```julia function updated_topology( old_topology::NotebookTopology{C}, notebook_cells::Iterable{C}, updated_cells::Iterable{C}; get_code_str::Function, get_code_expr::Function, get_cell_disabled::Function=c->false, ) where C <: AbstractCell ``` Return a copy of `old_topology`, but with new reactivity information from `updated_cells` taken into account. This function is used when cell code changes. `notebook_cells` should contain all cells in the reactive document. `updated_cells` contains the cells that changed (e.g. because they were edited). The functions `get_code_str` and `get_code_expr` should return the code string and parsed expression for a given cell. `get_cell_disabled` should return `true` if a cell is disabled, defaults to `false`. """ function updated_topology( old_topology::NotebookTopology{C}, notebook_cells, updated_cells; get_code_str::Function, get_code_expr::Function, get_cell_disabled::Function=c->false, ) where C <: AbstractCell updated_codes = Dict{C,ExprAnalysisCache}() updated_nodes = Dict{C,ReactiveNode}() for cell in updated_cells # TODO this needs to be extracted somehow old_code = old_topology.codes[cell] new_code_str = get_code_str(cell) if old_code.code !== new_code_str parsedcode = get_code_expr(cell) new_code = updated_codes[cell] = ExprAnalysisCache(new_code_str, parsedcode) new_reactive_node = ExpressionExplorer.compute_reactive_node(ExpressionExplorerExtras.pretransform_pluto(new_code.parsedcode)) updated_nodes[cell] = new_reactive_node elseif old_code.forced_expr_id !== nothing # reset computer code updated_codes[cell] = ExprAnalysisCache(old_code; forced_expr_id=nothing, function_wrapped=false) end end old_cells = all_cells(old_topology) removed_cells = setdiff(old_cells, notebook_cells) if isempty(removed_cells) # We can keep identity new_codes = merge(old_topology.codes, updated_codes) new_nodes = merge(old_topology.nodes, updated_nodes) else new_codes = merge(setdiffkeys(old_topology.codes, removed_cells), updated_codes) new_nodes = merge(setdiffkeys(old_topology.nodes, removed_cells), updated_nodes) end new_unresolved_set = setdiff!( union!( Set{C}(), # all cells that were unresolved before, and did not change code... Iterators.filter(old_topology.unresolved_cells) do c !haskey(updated_nodes, c) end, # ...plus all cells that changed, and now use a macrocall... Iterators.filter(updated_cells) do c !isempty(new_nodes[c].macrocalls) end, ), # ...minus cells that were removed. removed_cells, ) new_disabled_set = setdiff!( union!( Set{C}(), # all cells that were disabled before... old_topology.disabled_cells, # ...plus all cells that changed... updated_cells, ), # ...minus cells that changed and are not disabled. Iterators.filter(!get_cell_disabled, updated_cells), ) unresolved_cells = if new_unresolved_set == old_topology.unresolved_cells old_topology.unresolved_cells else ImmutableSet(new_unresolved_set; skip_copy=true) end disabled_cells = if new_disabled_set == old_topology.disabled_cells old_topology.disabled_cells else ImmutableSet(new_disabled_set; skip_copy=true) end cell_order = if old_cells == notebook_cells old_topology.cell_order else ImmutableVector(notebook_cells) # makes a copy end NotebookTopology{C}(; nodes=new_nodes, codes=new_codes, unresolved_cells, disabled_cells, cell_order, ) end _d&