5

The %load line-magic command loads the content of a given file into the current cell, for instance, executing:

[cell 1]    %load hello_world.py

... transform the cell into:

[cell 1]    # %load hello_world.py
            print("hello, world")

I would like to create a %load_next line-magic command which would instead load this file into the next cell. For example, executing cell 1 in the following notebook:

[cell 1]    %load_next hello_world.py

[cell 2]    print("hello, cruel world")  # original content

... would keep cell 1 unchanged and update cell 2 with the new content:

[cell 1]    %load_next hello_world.py

[cell 2]    print("hello, world")

I have tried this:

from IPython.core.magic import Magics, magics_class, line_magic
from pathlib import Path

@magics_class
class MyMagics(Magics):

    @line_magic
    def load_next(self, line):
        new_content = Path(line).read_text()
        self.shell.set_next_input(new_content, replace=False)

ip = get_ipython()
ip.register_magics(MyMagics)

But it inserts the content between the current and the next cell:

[cell 1]    %load_next hello_world.py

[cell 2]    print("hello, world")

[cell 3]    print("hello, cruel world")  # original content

Is it possible to make it either replace the next cell, or delete the next cell before inserting it?

Aristide
  • 3,606
  • 2
  • 30
  • 50
  • self.shell.set_next_input(new_content, replace=False) have you tried to set replace=True ? – sebrojas Jan 06 '21 at 13:58
  • @sebrojas It replaces the current cell. I would like to replace the next cell. – Aristide Jan 06 '21 at 14:12
  • Does this help https://stackoverflow.com/questions/27952428/programmatically-get-current-ipython-notebook-cell-output/27952661#27952661 – Joe Ferndz Jan 13 '21 at 07:44
  • @JoeFerndz I don't think so, AFAIK this notation deals with the value of a previous Python expression, I am interested in cell contents. – Aristide Jan 13 '21 at 17:13

1 Answers1

1

You can run below script. There is no way to get all cells, so I decided to run javascript code to remove the next cell. Js part finds all cells and remove the next cell from the current cell. I have tested on Jupyter Notebook and Jupyter Lab.

from IPython.display import display, HTML, Javascript
from IPython.core.magic import Magics, magics_class, line_magic
from pathlib import Path

@magics_class
class MyMagics(Magics):

    @line_magic
    def load_next(self, line):
        js_script = r"""<script>
            
            if (document.getElementById('notebook-container')) {
                //console.log('Jupyter Notebook');
                allCells = document.getElementById('notebook-container').children;
                selectionClass = /\bselected\b/;
                jupyter = 'notebook';
            }
            else if (document.getElementsByClassName('jp-Notebook-cell').length){
                //console.log('Jupyter Lab');
                allCells = document.getElementsByClassName('jp-Notebook-cell');
                selectionClass = /\bjp-mod-selected\b/;
                jupyter = 'lab';
            }
            else {
                console.log('Unknown Environment');
            }

            if (typeof allCells !== 'undefined') {
                for (i = 0; i < allCells.length - 1; i++) {
                    if(selectionClass.test(allCells[i].getAttribute('class'))){
                        allCells[i + 1].remove();
                        
                        // remove output indicators of current cell
                        window.setTimeout(function(){
                            if(jupyter === 'lab') {
                                allCells[i].setAttribute('class', allCells[i].getAttribute('class') + ' jp-mod-noOutputs');
                                allCells[i].getElementsByClassName('jp-OutputArea jp-Cell-outputArea')[0].innerHTML = '';
                            } else if(jupyter === 'notebook'){
                                allCells[i].getElementsByClassName('output')[0].innerHTML = '';
                            }
                        }, 20);
                        
                        break;
                    }
                }
            }
            </script>"""
        
        # remove next cell
        display(HTML(js_script))
        
        new_content = Path(line).read_text()
        self.shell.set_next_input(new_content, replace=False)

ip = get_ipython()
ip.register_magics(MyMagics)
Ismail Durmaz
  • 2,521
  • 1
  • 6
  • 19
  • Thanks a lot, it's terrific! I would not be able to use Javascript to do it. It works perfectly, and with the NbExtension "Initialization cells" will allow me to create Jupyter notebooks which automatically update on opening. One nitpick: after execution, the vertical space below the magic cell is greater than normal. Do you have any idea why? – Aristide Jan 13 '21 at 17:11
  • @Aristide I removed the selected cell's output indicators. Remove script is executed 20 milliseconds after execution. Please use the whole js_script content `js_script = r"""..... """`. It is added `r` identifier that is for backslashes. – Ismail Durmaz Jan 13 '21 at 21:11