Assigning styling based on cell metadata & firing logic every time a notebook is loaded

Hi!

I am working on a JupyterLab extension that mimics the bookmarks extension in VSCode, but I am running into a problem / point of confusion. Hopefully, somewhere can point me in the right direction!

Specifically, I want to apply some simple formatting to bookmarked cells, which is easy to achieve through a “bookmarked” class and some CSS. The challenge I am running into is that I want my bookmarks to be persistent, so I keep track of whether a cell is bookmarked or not through a custom metadata flag.

As far as I can tell, there is no way to apply styling to a cell based on its metadata. Based on a Stackoverflow thread I found (link), it appears that I need to reapply the bookmarked class to every cell with the metadata flag whenever a notebook is loaded. Is that right?

If so, then the question becomes, how do I listen for a new notebook to be opened and fire my logic to apply the classes? The SO post includes a potential solution, but to avoid unnecessary overhead, I’d ideally only like to fire this logic once every time a notebook is opened.

I have searched through existing extensions, the JupyterLab source code, and the documentation, and I am fairly certain that I will need to use currentChanged (i.e., some form of signal interaction). My problem, however, is that my code will always try to link itself to the signal before the notebookTracker/shell is initialized, causing it to error out.

E.g., if I include this in my code (where labShell: ILabShell)

    if (labShell) {
        labShell.currentChanged.connect((_, update) => {
          console.log('Changed');
        });
    } else {
        console.log('labShell is null');
    }

It will always yield the “LabShell is null” condition because it tries to reach labShell before it is initialized.

Clearly, I am lacking some understanding here, so a pointer would be much appreciated.

Thanks!!

1 Like

Would NotebookTracker work in this case? Here is an example: etc_jupyterlab_awareness/index.ts at 54bebe780d1df11912f8610066dc5702f170261e · educational-technology-collective/etc_jupyterlab_awareness · GitHub

2 Likes

I agree with @adpatter that NotebookTracker might be a better point of entry (in particular NotebookTracker.widgetAdded), but if you need an instantiated labShell, I think that you may want to use app.shell (where app is JupyterFrontEnd instance).

I would also take a guess that labShell is empty because ILabShell token is missing in your requires, or it is on a wrong position. We could only know for certain if you can show more code.

1 Like

Thanks @adpatter and @krassowski, this was exactly what I needed, problem solved!

I copied the code exactly as in the example of @adpatter, but it still did not work, which indicated that it had to be something else. This is where your last comment gave me the lightbulb moment @krassowski, the issue was with a left-over ISettingRegistry item in the activate statement.

It looked like this:

const plugin: JupyterFrontEndPlugin<void> = {
  id: 'code-bookmarks',
  requires: [INotebookTracker],
  autoStart: true,
  activate: (
        app: JupyterFrontEnd, 
        settingRegistry: ISettingRegistry | null,
        notebookTracker: INotebookTracker,
    )  => { ....

Removing the settingRegistry: ISettingRegistry | null, line immediately resolved the issue and caused everything to work as expected. I honestly can’t remember why I had that in there in the first place, must have been a left-over from a previous experiment :man_facepalming:.

Mystery solved, I had tried basically every possible way of using notebookTracker or labShell last night and I just couldn’t get it to work.

Also notebookTracker.widgetAdded definitely sounds like the way to go, so I’ll work on implementing it based on that.

2 Likes

I think we’ve all done that one at some point or another. :slight_smile: