Insert!, cross-module struct mutation and symbol reference

Barkground:

The lecture notes for an open courseware class I’m working through uses an interactive Pluto notebook. I wanted to add an empty cell to my local copy of this notebook to work on one of the programming exrcises, but the keyboard gesture for doing that wasn’t working for me (in Google Chrome).

My question isn’t about the Pluto UI though. I saw the structure of the notebook file and decided it would be easy enough to write a command line tool that would add a cell to the notebook so long as the notebook wasn’t currently being served. Before diving in to doing uninforned file manipulation, I looked through Pluto.jl and found it was easy to identify a set of functions my program could call into to open and manipulate the notebook.

My Problem:

The present state of my effort is on GitHub:

GitHub - MarkNahabedian/PlutoTool.jl: A command line utility for doiing simple manipulations on Pluto notebook files.

Im using insert! to insert a new Pluto.Cell into the Pluto.Notebook’s cells vector. I don’t understand why this isn’t working in my application but does work in some stripped down test cases.

My problem is that the length assertion is failing:

“”" new_cell before/after notebook_path relative_to_cell_id
Insert a new, enpty cell before or after the cell specified by existing_cell_id.
The id of the new cell is returned.
“”"
function new_cell(ctx::Context, before_after::String, notebook_path::String, relative_to_cell_id::String)::String
notebook = get_notebook(notebook_path)
index = find_cell(notebook, relative_to_cell_id)
rel = validate_option(before_after, :before, :after)
@assert index >= 1
@assert index <= length(notebook.cells)
if rel == :after
index = index + 1
end
cell = Pluto.Cell()
len = length(notebook.cells)
insert!(notebook.cells, index, cell)
@assert length(notebook.cells) == len + 1
Pluto.save_notebook(notebook)
@printf(ctx.stdout, “Inserted new cell %s\n”, string(cell.cell_id))
return string(cell.cell_id)
end

I’ve tried some stripped down examples, which both work:

As a lisp programmer I’ve learned that “modify in place” operations might need to relocate the object (in this case insert! aND THE Notebook’S vector of Cells), so one should assign the modified result back where it came from. I don’t seem to be able to write to notebook.cells though. If I do

notebook.cells = insert!(notebook.cells, index, cell)

I get the error " type Notebook has no field cells" which is somewhat misdirecting in that I was able to read the cells field without error, just bot set it. I remember reading someplace that Julia didn’t allow cross-module struct mutations.

I also tried using setfield!, but I don’t know how to refer to :cell from the Pluto module rather than my own.

I’ve tried searching the Pluto package for code that modifies Notebook.cells but didn’t find anything. It must be there someplace though otherwise how can people edit their notebooks.

That’s not necessary in Julia, at lest not in any circumstance I’ve come across.

Mm, no, that’s not a thing. There are no issues or restrictions on modifying mutable structs, regardless of what module your code or the definition is in.

Likewise, this is a red herring. The particular module in which the code lives is of no consequence.

This is because the .cells accessor is actually provided by this getproperty definition:

There’s no setproperty! implementation, presumably because Pluto.jl intentionally doesn’t support that particular way of modifying the cells.

That function also reveals why mutating notebook.cells doesn’t work. The getproperty method means that notebook.cells returns a brand new vector (the output of map). You can mutate that all you want, but it has no effect on the notebook itself.

It looks like Pluto.jl doesn’t support what you’re trying to do, but it doesn’t look like it would be too hard to add it. As far as I can tell, it would require modifying both notebook.cells_dict and notebook.cell_order. That would probably best be done by adding a new insert_cell! function in Pluto.jl itself, but there’s no reason you can’t prototype that function in your own code (as you’re doing now).

3 Likes

Thanks Robin. That makes things a lot clearer.

I’m hoping to not have to modify the Pluto package.

Now that you provided me with a much better understanding of what’s going on, I’ll try to push forward.

Thanks again Robin.

This would have gone a lot more smoothly if the version of the Pluto sources I was looking at were the same version as julia was using. The old version is now wiped from my laptop.