Skip to content

Conversation

@jonasseglare
Copy link

This pull request adds code that recognizes blocks starting with if False: as code cells. This is an attempt at having something similar to rich comments commonly used in Clojure. I currently use this code in my Emacs setup and find it very convenient. But you do not have to accept this PR to your repo, it is just a suggestion in case others could also find use for this code.

The idea is that if False: prevents the code inside from being evaluated, similar to the (comment ...) macro in Clojure. In Clojure, it is common to add such comments wrapping code at the end of modules for examples and experiments and editors can be configured to execute the code in those comments when the cursor is placed in the comment. With the code in this pull request, if False-blocks are recognized as cells and the code wrapped inside those blocks is evaluated when we send the cell to the REPL. But the code is not executed if the module is run normally, e.g. by importing it from another module.

Screenshot from 2023-02-16 19-38-26

@twmr
Copy link
Owner

twmr commented Feb 16, 2023

Hi! Thx a lot for your contribution!

if False-blocks are recognized as cells and the code wrapped inside those blocks is evaluated when we send the cell to the REPL

Very nice! I'll test this change in the coming days and will then probably merge it. Thx!

(forward-char 1))


(defun python-cell-beginning-of-cell--sub (cellbreak-goto-function)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have doubts about the naming of this function and the refactoring I did related to it. Essentially, I refactored the function python-cell-beginning-of-cell into more functions to have better control of where at the beginning the cursor is placed:

  • When I navigate to a cell, I want the cursor where the actual code starts, that is after the cellbreak string (if False... or ##....)
  • When I send a cell to the interpreter, I want the cursor to be at the very beginning of the cell, including the cellbreak string.

To accomplish this, the function python-cell-beginning-of-cell--sub accepts a function as argument that will put the cursor in the right place in case a cellbreak is detected. Either we pass python-cell-beginning-of-matched-cellbreak or python-cell-end-of-matched-cellbreak to that function. The original python-cell-beginning-of-cell function passes in the former.

Is there a better way to structure this code? What about naming? Feel free to comment on this and I will address it.

Copy link
Author

@jonasseglare jonasseglare Feb 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if the code would be more clear if I instead had a function something like

(defun python-cell-detect-beginning-of-cell ()
  (end-of-line)
    (if (re-search-backward python-cell-cellbreak-regexp nil t)
        t
      (progn (goto-char (point-min)) nil)))

that returns a truthy value in case the beginning of the cell is detected. And then, in that case, the caller of that function can move the cursor:

(when (python-cell-detect-beginning-of-cell)
   (python-cell-end-of-matched-cellbreak))

This might make the code more readable but I am really not sure.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • When I send a cell to the interpreter, I want the cursor to be at the very beginning of the cell, including the cellbreak string.

So you want to send the if False string to the python shell s.t. the body of the if False statement is not executed by the python shell? Why that?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because in the function python-cell-shell-send-cell we call python-cell-preprocess-cell that will replace if False: by if True:. Because of this replacement, the body of the cell will be run.

For instance, if we have a cell

if False: # load the dataset
    ds = load_dataset("data.csv")

the string sent to Python becomes

if True: # load the dataset
    ds = load_dataset("data.csv")

Is there a better way to accomplish the effect of having cells with if False: executed?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thx for the explanation! Now it starts to make sense.

Is there a better way to accomplish the effect of having cells with if False: executed?

I don't think so.

BTW It would be nice if there were a way to detect the end of the "rich-comment". I guess that the recently added support for tree-sitter in emacs-29 could be used for that.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, detecting the end of rich-comments would mean that the following code

if False:
    dataset = load_dataset("validation.py")

results = algorithm.evaluate(dataset);

would actually become two cells, right? The two cells being

if False:
    dataset = load_dataset("validation.py")

and

results = algorithm.evaluate(dataset);

I am not sure how useful that would be and it would probably be tricky to get it work. So maybe it is something to consider outside of the scope of this PR.

@jonasseglare
Copy link
Author

Thanks @Thisch for python-cell.el, I have been using it for years. Feel free to make any changes you like to the code. Or you can ask me to change it, whatever you prefer.

(group-n 1 (and "#" (or (and "#" space (* (not (any "\n"))))
(and " <" (or "codecell" "markdowncell") ">"))
line-end)))
(group-n 1 (and "if False:"
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could allow any number of spaces between if and False but that would make the logic inside python-cell-preprocess-cell more complicated. And I don't know if there would be a need for any other code formatting than if False: so maybe it is good enough to only detect exactly this string.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it suffices to hard code "if False:" as you did. Why do you have to use group-n in the rx macro, isn't the following sufficient?

  (rx line-start (* space)
      (group (and (or (and "#" (or (and "#" space (* (not (any "\n"))))
                                   (and " <" (or "codecell" "markdowncell") ">")))
                      "if False:")
                  line-end)))

Copy link
Author

@jonasseglare jonasseglare Feb 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a good point. I initially wrote something similar to that code. But I think that this version with only one group is going to detect also rich comments preceded by space, e.g. if False:. I wanted to prohibit that, because evaluating that cell would probably be invalid syntax unless we remove the indentation before sending it to the Python interpreter. If we were to support indentation in front of if False:, we could maybe have cells defined inside other code such as functions, but I think that would complicate things and not be very useful, so I disallow it.~~

Edited: Hmm, I still have doubts and have not settled. Do we want to detect rich comments if they are indented?

The (group-n 1 ...) is needed over (group ...) in order for the code that I wrote to work properly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants