Last active
April 19, 2024 00:40
-
-
Save abdullahkhalids/83055b1abbd2cdf2416a480d046136e1 to your computer and use it in GitHub Desktop.
Markdown-notebook minor mode for emacs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;; markdown-notebook mode | |
;; Helps you to insert python code blocks and execute their results | |
;; and place output back inside the document. | |
;; Install | |
;; Put this file somewhere and then add the following line to your emacs.el | |
;; (load-file "~/path/to/mdnb.el") | |
;; Help | |
;; Load the minor mode with M-x markdown-notebook-mode | |
;; There are only three commands. | |
;; "C-c b": mdnb-insert-python-code-block will insert a code block. | |
;; "C-c C-c" mdnb-send-to-python-shell will execute the code inside a python | |
;; shell. Shell is shared by entire | |
;; document. Code output is put inside a | |
;; code block right after. | |
;; "C-c C-r" mdnb-restart-python-shell will restart the shell. | |
;; License | |
;; GPLv3 Abdullah Khalid 2023 (abdullahkhalid.com) | |
(define-minor-mode markdown-notebook-mode | |
"Markdown notebook mode" | |
:lighter " mdnb" | |
:keymap (let ((map (make-sparse-keymap))) | |
;; Key bindings | |
(define-key map (kbd "C-c b") 'mdnb-insert-python-code-block) | |
(define-key map (kbd "C-c C-c") 'mdnb-send-to-python-shell) | |
(define-key map (kbd "C-c C-r") 'mdnb-restart-python-shell) | |
map) | |
:global nil | |
:init-value nil) | |
;; Utility functions | |
(defun mdnb-line-nonempty (&optional line-n) | |
"Check if the line relative to current line is non-empty." | |
(save-excursion | |
(forward-line (or line-n 0)) | |
(beginning-of-line) | |
(re-search-forward "." (line-end-position) t))) | |
;; Core functions | |
(defun mdnb-codeblock-start-search () | |
"Look back to see if can find a valid code block start." | |
(save-excursion | |
(let ( | |
(cur-pos (point)) | |
(start-marker-pos (re-search-backward "^```python$" nil t)) | |
) | |
(goto-char cur-pos) | |
(if (null (re-search-backward "^```$" start-marker-pos t)) | |
start-marker-pos | |
nil)))) | |
(defun mdnb-codeblock-end-search () | |
"Look forward to see if can find a valid code block end." | |
(save-excursion | |
(let ( | |
(cur-pos (point)) | |
(end-marker-pos (re-search-forward "^```$" nil t)) | |
) | |
(goto-char cur-pos) | |
(if (null (re-search-forward "^```python$" end-marker-pos t)) | |
end-marker-pos | |
nil)))) | |
(defun mdnb-start-python-shell () | |
"Starts a Python shell in a background buffer." | |
(interactive) | |
(unless (get-buffer "*Python*") | |
(run-python))) | |
(defun mdnb-restart-python-shell () | |
"Restart python shell." | |
(interactive) | |
(kill-process "Python") | |
(sleep-for 0.05) | |
(kill-buffer "*Python*") | |
(mdnb-start-python-shell)) | |
(defun mdnb-inside-code-block () | |
"Checks if point is inside a code block." | |
(let ( | |
(start-pos (mdnb-codeblock-start-search)) | |
(end-pos (mdnb-codeblock-end-search)) | |
) | |
(and start-pos end-pos))) | |
(defun mdnb-insert-python-code-block () | |
"Inserts a Python code block in Markdown format. If you run this on a | |
blank line, and the previous line is also blank, the code block is | |
added at this positon. Otherwise, we search forward till an empty | |
line is found and add the code block there. If point is inside a | |
code block, nothing happens." | |
(interactive) | |
(if (mdnb-inside-code-block) | |
(message "inside code block.") | |
(progn | |
(end-of-line) | |
(if (= (point) (point-max)) | |
(newline)) | |
(while (mdnb-line-nonempty) | |
(forward-line)) | |
(if (mdnb-line-nonempty -1) | |
(newline)) | |
(insert "```python\n\n```\n") | |
(forward-line -2)))) | |
(defun mdnb-send-to-python-shell () | |
"If inside a python code block, send the code to the python shell. If not | |
inside a shell, then do nothing." | |
(interactive) | |
(let ( | |
(start-pos (mdnb-codeblock-start-search)) | |
(end-pos (mdnb-codeblock-end-search)) | |
) | |
(if (and start-pos end-pos) | |
(let ( | |
(start-code-pos (progn | |
(goto-char start-pos) | |
(forward-line) | |
(line-beginning-position))) | |
(end-code-pos (progn | |
(goto-char end-pos) | |
(forward-line -1) | |
(line-end-position))) | |
) | |
(setq code-input (buffer-substring-no-properties | |
start-code-pos end-code-pos)) | |
;; Send code block to Python shell | |
(setq mdnb-output (python-shell-send-string-no-output code-input)) | |
;; Create a new code block below and paste the output inside | |
(if (not (string-equal mdnb-output "")) | |
(progn | |
(goto-char end-pos) | |
(forward-line) | |
(setq output-start-pos (point)) | |
(if (string-equal (buffer-substring-no-properties | |
(line-beginning-position) (line-end-position)) | |
"```") | |
(progn | |
(forward-line) | |
(setq output-end-pos (re-search-forward "^```$")) | |
(delete-region output-start-pos output-end-pos) | |
)) | |
(insert "```\n") | |
(insert mdnb-output) | |
(insert "\n```") | |
)))))) | |
;; Add hook | |
(add-hook 'markdown-notebook-mode-hook 'mdnb-start-python-shell) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment