Skip to content

Instantly share code, notes, and snippets.

@abdullahkhalids
Last active April 19, 2024 00:40
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save abdullahkhalids/83055b1abbd2cdf2416a480d046136e1 to your computer and use it in GitHub Desktop.
Save abdullahkhalids/83055b1abbd2cdf2416a480d046136e1 to your computer and use it in GitHub Desktop.
Markdown-notebook minor mode for emacs
;; 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