Skip to content

Instantly share code, notes, and snippets.

@mkoeppe
Created June 12, 2023 17:46
Emacs macros for SageMath `# optional` maintenance
(defun sage-copy-optional-annotation ()
"In a 'sage: ' line of a docstring, copy '# optional' from a previous line and
advance to the end of the next 'sage: ' line or to the end of the current docstring.
If invoked elsewhere, just advance to the end of the next 'sage: ' line."
(interactive)
(if (save-excursion (beginning-of-line)
(looking-at " *sage:"))
(multiple-value-bind (tab-stop-list text advance)
(save-excursion
(previous-line)
(end-of-line)
(condition-case nil
(re-search-backward "# *optional.*$")
(error
(values '(88) ; alignment point for modularization "# optional"s
"# optional - "
nil))
(:success
(values (list (- (match-beginning 0)
(save-excursion
(beginning-of-line) (point))))
(match-string-no-properties 0)
t))))
(end-of-line)
(just-one-space)
(tab-to-tab-stop)
(insert text)
(when advance
(re-search-forward "sage: \\|\"\"\"")
(end-of-line)))
(re-search-forward "sage:")
(end-of-line)))
(defun sage--align-comment (alignment)
(let ((tab-stop-list alignment))
(just-one-space 1)
(tab-to-tab-stop)
(when (ignore-errors (looking-at "# optional[- ]*"))
(goto-char (match-end 0))
(while (ignore-errors (re-search-forward " *# *optional[- ]*" (save-excursion (end-of-line) (point))))
(replace-match " ")))
(re-search-forward "sage: \\|\"\"\"")
(end-of-line)))
(defun sage-align-optional-annotation ()
(interactive)
(cond
((save-excursion (beginning-of-line) (looking-at " *sage:"))
(beginning-of-line)
(when (ignore-errors (re-search-forward "[^ ]\\( \\)#"
(save-excursion (end-of-line) (point))))
(goto-char (match-beginning 1))
(insert " "))
(condition-case nil
(re-search-forward "# optional" (save-excursion (end-of-line) (point)))
(:success
(goto-char (match-beginning 0))
(if (looking-at "# optional[- ]*\\(sage\\|numpy\\|scipy\\|sympy\\|fpylll\\|mpmath\\|pplpy\\|sphinx\\|pexpect\\|networkx\\|primecountpy\\)")
(sage--align-comment ; standard packages
(cond ((string-match "quadratic_form__" (buffer-file-name))
'(84 96 116)) ; these are imported into a class => one indentation level deeper
(t
'(88 100 120)))) ; 88 = alignment point for modularization "# optional"s
; in sphinx furo style
(sage--align-comment
(cond ((string-match "geometry/" (buffer-file-name))
'(72 ; "# optional - pynormaliz" fully visible
77 ; "# optional - pynormaliz sage.rings.number_field" aligns with "# optional - sage.rings.number_field"
80
84))
(t '(80) ;'(64)
)))))
(error
(end-of-line)
(re-search-forward "\\(# optional\\|[^ ]\\( \\)#\\)")
(end-of-line))))
(t
(re-search-forward "sage:")
(end-of-line))))
(defun sage-newline-keep-comment ()
(interactive)
(let ((pt (point))
(eol (save-excursion (end-of-line)
(point))))
(condition-case nil
(re-search-forward "\\([^#]*\\)\\(#.*\\)?$" eol)
(error
(newline))
(:success
(let ((text (match-string 1)))
(replace-match (make-string (length text) ? )
nil nil nil 1)
(end-of-line)
(newline)
(py-indent-line)
(insert "....: ")
(save-excursion (insert text)))))))
(defun sage-delete-indentation (&optional arg beg end)
(interactive
(progn (barf-if-buffer-read-only)
(cons current-prefix-arg
(and (use-region-p)
(list (region-beginning) (region-end))))))
(if (save-excursion (beginning-of-line) (looking-at " *\\(sage\\|[.][.][.][.]\\): "))
(let ((fill-prefix (match-string 0)))
(delete-indentation arg beg end))
(delete-indentation arg beg end)))
(with-eval-after-load "python-mode"
(define-key python-mode-map (kbd "C-M-;") 'sage-copy-optional-annotation)
(define-key python-mode-map (kbd "C-M-'") 'sage-align-optional-annotation)
(define-key python-mode-map (kbd "C-M-<return>") 'sage-newline-keep-comment)
(define-key python-mode-map (kbd "M-^") 'sage-delete-indentation)
)
(font-lock-add-keywords 'python-mode
'(("^ *\\(sage: \\|[.][.][.][.]: \\)" 1
'font-lock-warning-face prepend)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment