branch: externals/literate-scratch
commit 2317f456e6047b4d595b7c5082dc8106dc1fc01d
Author: Sean Whitton <[email protected]>
Commit: Sean Whitton <[email protected]>
* literate-scratch: Import version 2.0.
---
literate-scratch.el | 144 +++++++++++++++++++++++++++++-----------------------
1 file changed, 81 insertions(+), 63 deletions(-)
diff --git a/literate-scratch.el b/literate-scratch.el
index e3ac223bbb..ddc2458216 100644
--- a/literate-scratch.el
+++ b/literate-scratch.el
@@ -1,11 +1,11 @@
;;; literate-scratch.el --- Lisp Interaction w/ text paragraphs -*-
lexical-binding: t -*-
-;; Copyright (C) 2023-2024 Free Software Foundation, Inc.
+;; Copyright (C) 2023-2025 Free Software Foundation, Inc.
;; Author: Sean Whitton <[email protected]>
;; Maintainer: Sean Whitton <[email protected]>
;; Package-Requires: ((emacs "29.1"))
-;; Version: 1.0
+;; Version: 2.0
;; URL:
https://git.spwhitton.name/dotfiles/tree/.emacs.d/site-lisp/literate-scratch.el
;; Keywords: lisp text
@@ -49,85 +49,103 @@
;;; News:
+;; Ver 2.0 2025/04/18 Sean Whitton
+;; Rewrite core algorithm to use comment starters not comment fences,
+;; and therefore no `syntax-propertize-extend-region-functions' entry.
+;; Thanks to Stefan Monnier for comments which prompted this.
+;; Newly recognise `\\=`(' as starting paragraphs of Lisp.
;; Ver 1.0 2024/06/21 Sean Whitton
;; Initial release.
;; Thanks to Philip Kaludercic for review.
;;; Code:
-(defun literate-scratch--extend (start end)
- (save-excursion
- (let ((res1
- (and (goto-char start)
- (not (looking-at paragraph-separate))
- (and-let* ((new (car (bounds-of-thing-at-point 'paragraph))))
- (and (< new start)
- (setq start new)))))
- (res2
- (and (goto-char end)
- (not (looking-at paragraph-separate))
- (and-let* ((new (cdr (bounds-of-thing-at-point 'paragraph))))
- (and (> new end)
- (setq end new))))))
- (and (or res1 res2)
- (cons start end)))))
-
(defun literate-scratch--propertize (start end)
(goto-char start)
- (let ((start (1- start)))
- (catch 'finish
- (while t
- (when-let* ((comment-start (nth 8 (syntax-ppss))))
- (put-text-property (1- (point)) (point) 'syntax-table
- (eval-when-compile
- (string-to-syntax "!")))
- (put-text-property comment-start (point) 'syntax-multiline t))
- (forward-paragraph 1)
- (backward-paragraph 1)
- (unless (> end (point) start)
- (throw 'finish nil))
- (setq start (point))
- (unless
- (save-excursion
- (catch 'done
- (while t
- ;; Examine the syntax of the paragraph's first char.
- ;; If it's whitespace, we need to check the previous
- ;; paragraph, to handle multiple paragraphs within a defun.
- (let ((syn
- (char-syntax
- (char-after
- ;; (1+ point) unless at end-of-buf or on first line
- ;; of a paragraph beginning right at beg-of-buf.
- (if (looking-at "\\`[[:space:]]*[^[:space:]\n]")
- (point)
- (1+ (point)))))))
- (cond ((bobp) (throw 'done (memq syn '(?\( ?<))))
- ((memq syn '(?\( ?<)) (throw 'done t))
- ((not (eq syn ?\s)) (throw 'done nil))))
- (backward-paragraph 1))))
- (put-text-property (point) (1+ (point)) 'syntax-table
- (eval-when-compile
- (string-to-syntax "!"))))
- (forward-paragraph 1))))
+ (unless (bolp)
+ ;; Skip this line if START is already later than where we'd put the text
+ ;; property (i.e., make a modification). Else just ensure BOL.
+ (back-to-indentation)
+ (forward-line (if (>= (point) start) 0 1)))
+ (while (and (not (eobp)) (>= end (point)))
+ ;; Here we do want to treat `]' the same as `)'.
+ (skip-syntax-forward "-)")
+ (unless (looking-at paragraph-separate)
+ (back-to-indentation)
+ (let ((1st (point)))
+ ;; 1ST's line ...
+ (if (eq (pos-bol) (point-min))
+ ;; ... is the first line of the buffer.
+ (literate-scratch--put-comment-start 1st)
+ (save-excursion
+ (forward-line -1)
+ (cond ((not (looking-at paragraph-separate))
+ ;; ... is within a paragraph.
+ ;; Then make 1ST's line a block comment line if and only
+ ;; if the line preceding it is a block comment line too.
+ (back-to-indentation)
+ (when (literate-scratch--comment-start-p (point))
+ (literate-scratch--put-comment-start 1st t)))
+ ((eq (pos-bol 2) 1st)
+ ;; ... starts a paragraph and is unindented.
+ (literate-scratch--put-comment-start 1st))
+ ((zerop (car (syntax-ppss 1st)))
+ ;; ... starts a paragraph and is indented, but could not
+ ;; be a code paragraph within a multi-paragraph defun.
+ (literate-scratch--put-comment-start 1st)
+ (syntax-ppss-flush-cache 1st)))))))
+ (forward-line 1))
;; Now also call the usual `syntax-propertize-function' for this mode.
(elisp-mode-syntax-propertize start end))
+(defun literate-scratch--indent-line ()
+ ;; `lisp-indent-line' has hardcoded behaviour for lines starting with a
+ ;; single comment character, which includes the ones we've marked.
+ (or (save-excursion
+ (back-to-indentation)
+ (literate-scratch--comment-start-p (point)))
+ (lisp-indent-line)))
+
+(defun literate-scratch--put-comment-start (pos &optional force)
+ "Mark POS's line as a block comment line depending on the char(s) at POS.
+If FORCE is non-nil, instead unconditionally mark POS's line as a block
+comment line. Moves point to POS if it is not already there."
+ (goto-char pos)
+ ;; We consider only non-self-evaluating forms to compose Lisp paragraphs.
+ ;; It might be useful to be able to prepare forms starting with `\\='(' or
+ ;; `[' in *scratch* with the benefit of Paredit, and then kill them and yank
+ ;; into other buffers. On the other hand, you might want to start plain
+ ;; text paragraphs with those strings.
+ ;;
+ ;; Treat a backtick on its own as Lisp so that when an immediate following
+ ;; open parenthesis is typed, Paredit inserts a closing parenthesis too.
+ (when (or force (not (looking-at "`$\\|`?(\\|;")))
+ (put-text-property pos (1+ pos) 'syntax-table
+ (eval-when-compile (string-to-syntax "<")))
+ ;; Mark the newline as not a comment ender, as it usually is for Elisp.
+ ;; Then only the second newline of the two newlines at the end of a text
+ ;; paragraph is a comment ender. This means that when point is between
+ ;; the two newlines, i.e. at the beginning of an empty line right after
+ ;; the text paragraph, `paredit-in-comment-p' still returns t, and so
+ ;; \\`(' and \\`[' insert only single characters.
+ (put-text-property (pos-eol) (1+ (pos-eol)) 'syntax-table
+ (eval-when-compile (string-to-syntax "-")))))
+
+(defun literate-scratch--comment-start-p (pos)
+ (equal (get-text-property pos 'syntax-table)
+ (eval-when-compile (string-to-syntax "<"))))
+
;;;###autoload
(define-derived-mode literate-scratch-mode lisp-interaction-mode
"Lisp Interaction"
"Variant `lisp-interaction-mode' designed for the *scratch* buffer.
-
-Paragraphs that don't start with `(' or `;' are treated as block comments.
+Paragraphs that don't start with `(', `\\=`(' or `;' become block comments.
This makes it easier to interleave paragraphs of plain text with Lisp.
-You can enable this mode by customizing the variable `initial-major-mode'
+You can enable this mode by customising the variable `initial-major-mode'
to `literate-scratch-mode'."
- (add-hook 'syntax-propertize-extend-region-functions
- #'syntax-propertize-multiline t t)
- (add-hook 'syntax-propertize-extend-region-functions
- #'literate-scratch--extend t t)
- (setq-local syntax-propertize-function #'literate-scratch--propertize))
+ (setq-local syntax-propertize-function #'literate-scratch--propertize
+ indent-line-function #'literate-scratch--indent-line))
(provide 'literate-scratch)