emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[elpa] externals/flylisp 20fe3e7 1/5: flylisp: New package. See announce


From: Stefan Monnier
Subject: [elpa] externals/flylisp 20fe3e7 1/5: flylisp: New package. See announcement: http://lists.gnu.org/archive/html/emacs-devel/2013-12/msg00486.html
Date: Tue, 1 Dec 2020 15:57:27 -0500 (EST)

branch: externals/flylisp
commit 20fe3e77bb73773c8678e65ae38020b0b7f996eb
Author: Barry <gundaetiapo@gmail.com>
Commit: Barry <gundaetiapo@gmail.com>

    flylisp: New package. See announcement: 
http://lists.gnu.org/archive/html/emacs-devel/2013-12/msg00486.html
---
 flylisp.el | 506 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 506 insertions(+)

diff --git a/flylisp.el b/flylisp.el
new file mode 100644
index 0000000..4c34d3b
--- /dev/null
+++ b/flylisp.el
@@ -0,0 +1,506 @@
+;;; flylisp.el --- Color unbalanced parentheses and parentheses inconsistent 
with indentation -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2013  Free Software Foundation, Inc.
+
+;; Author: Barry O'Reilly <gundaetiapo@gmail.com>
+;; Version: 0.2
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Colors mismatched open parentheses with fl-mismatched-face, red by
+;; default.
+;;
+;; Also colors open and close parentheses which are inconsistent with
+;; the indentation of lines between them with fl-inconsistent-face,
+;; orange by default. This is useful for the Lisp programmer who
+;; infers a close paren's location from the open paren and
+;; indentation. The coloring serves as a warning that the indentation
+;; misleads about where the close paren is. It may also help to
+;; localize the mistake, whether due to a misindented line or a
+;; misplaced paren.
+;;
+;; As an example, consider:
+;;
+;;   (aaa (bbb "word-a
+;;   word-b" (ccc 1
+;;                2)
+;;        fff))
+;;
+;; (aaa ...) and (ccc ...) are consistent, so are not colored.
+;; (bbb ...) is inconsistent because the indentation of fff is
+;; inconsistent with the actual location of the close paren. The open
+;; and close paren are thus colored with the fl-inconsistent-face.
+;; This example also shows that multi line strings don't cause an
+;; inconsistency.
+;;
+;; Currently, the package only detects close parens that are after the
+;; place indentation would predict. A planned feature is to also
+;; indicate when the close paren is before.
+;;
+;; Also planned is to color mismatched close parens.
+
+;;; Code:
+
+;; TODO: There are display problems with mismatched parens, due to the
+;; region not expanding enough, in turn due to an apparent syntax-ppss
+;; bug. See Emacs bug 16247.
+
+;; TODO: Algorithm doesn't account for close paren which is too soon.
+;;
+;; (abc
+;;   (def))
+;;   (ghi)
+;;
+;; (abc ...) are inconsistent parens because (ghi) is indented too far
+
+;; TODO: implement mismatched close parens
+
+;; TODO: Write tests:
+;;
+;;   ;; Expect (abc ...) is consistent, (def ...) is inconsistent:
+;;   (abc a-symbol (a-func-call "word-a
+;;   word-b" (def ghi
+;;           jkl)
+;;
+;;   ;; Expect (when ...) is inconsistent:
+;;   (when (and t
+;;         nil))
+;;   ;; After change, expect (when ...) is consistent and last paren 
mismatched:
+;;   (when (and t)
+;;         nil))
+;;
+;;   Given (a ...) inconsistent, change to (a ...(), and verify close
+;;   paren is consistent.
+
+(require 'cl-lib)
+(require 'jit-lock)
+
+(defgroup flylisp nil
+  "Color unbalanced parentheses and parentheses inconsistent with indentation."
+  :prefix "flylisp-"
+  :group 'paren-matching)
+
+(defgroup flylisp-faces nil
+  "Faces for flylisp package. "
+  :group 'flylisp
+  :group 'faces)
+
+(defface fl-inconsistent-face
+  '((((class color) (background light))
+     :foreground "dark orange")
+    (((class color) (background dark))
+     :foreground "orange"))
+  "Face applied to matching open and close parens whose placement
+is inconsistent with indentation."
+  :group 'flylisp-faces)
+
+(defface fl-mismatched-face
+  '((((class color) (background light))
+     :foreground "dark red")
+    (((class color) (background dark))
+     :foreground "red"))
+  "Face applied to a paren who has no match."
+  :group 'flylisp-faces)
+
+;; An open paren and algorithmic data about it.
+;;
+;; position is the position in the buffer of the open paren
+;;
+;; close is one of:
+;;   - nil if unknown
+;;   - the position before the matching close paren
+;;   - the symbol 'mismatched if no matching close paren exists
+;;
+;; column is the displayed column of the open paren in its logical
+;; line of the buffer
+;;
+;; inconsistent is whether the open paren's close paren is
+;; inconsistent with the indentation within the list defined by the
+;; parens. It is one of:
+;;   - nil if unknown or consistent
+;;   - an integer offset from the open position to the position of the
+;;     first inconsistency. This offset is also cached in the open
+;;     paren text properties for performance.
+(cl-defstruct fl--Open position close column inconsistent)
+
+(defsubst fl--colorize-inconsistent (open-obj)
+  "Colorize the fl--Open OPEN-OBJ as inconsistent."
+  (add-text-properties (fl--Open-position open-obj)
+                       (1+ (fl--Open-position open-obj))
+                       `(fl-inconsistency
+                         ,(fl--Open-inconsistent open-obj)
+                         font-lock-face
+                         fl-inconsistent-face
+                         rear-nonsticky
+                         t))
+  (add-text-properties (fl--Open-close open-obj)
+                       (1+ (fl--Open-close open-obj))
+                       `(font-lock-face
+                         fl-inconsistent-face
+                         rear-nonsticky
+                         t)))
+
+(defsubst fl--line-check-opens (open-stack)
+  "Check fl--Open objects of the OPEN-STACK list for
+consistency.
+
+The inconsistent==nil elements of OPEN-STACK must have columns
+that are strictly decreasing moving towards the tail (a necessary
+but not sufficient condition for being consistent). The
+implementation optimizes on this assumption.
+
+Call with point on the line being checked; puts point on the next
+line or EOB."
+  (let ((indent-pos (progn (back-to-indentation)
+                           (point)))
+        (indent-column (current-column))
+        (line-end (progn (end-of-line)
+                         (point))))
+    ;; Assess open-objs against indent-column
+    (unless (eq indent-pos line-end) ; Skip whitespace lines
+      ;; Since we're only interested in marking Opens inconsistent,
+      ;; the open-stack's documented property allows the iteration to
+      ;; stop at the first inconsistent==nil Open with small enough
+      ;; column.
+      (while (and open-stack
+                  (or (fl--Open-inconsistent (car open-stack))
+                      (<= indent-column
+                          (fl--Open-column (car open-stack)))))
+        ;; Check fl--Open-inconsistent to avoid excessive
+        ;; syntax-ppss when there's a lot of bad
+        ;; indentation.
+        (unless (or (fl--Open-inconsistent (car open-stack))
+                    ;; Multi line strings don't cause inconsistency
+                    (nth 3 (syntax-ppss indent-pos)))
+          (setf (fl--Open-inconsistent (car open-stack))
+                (- indent-pos (fl--Open-position (car open-stack)))))
+        (pop open-stack)))
+    ;; Go to next line. Since we already know line-end, use it
+    ;; instead of rescanning the line
+    ;;
+    ;; goto-char tolerates going beyond EOB
+    (goto-char (1+ line-end))))
+
+(defsubst fl--region-check-opens (downward-objs
+                                  upward-objs)
+  "Check inputted parens in a region for inconsistency, first
+going down in sexp depth then up per the DOWNWARD-OBJS and
+UPWARD-OBJS.
+
+Point must be at the start of the region to process and will end
+up near the end.
+
+DOWNWARD-OBJS is a list of fl--Open objects. Each must be a
+parent of the next in the list.
+
+UPWARD-OBJS is a list of fl--Open objects. Each must be a child
+of the next in the list."
+  (while downward-objs
+    (fl--line-check-opens upward-objs)
+    (while (and downward-objs
+                (< (fl--Open-position (car downward-objs))
+                   (point)))
+      (push (pop downward-objs)
+            upward-objs)))
+  (while (and upward-objs
+              (number-or-marker-p (fl--Open-close (car upward-objs))))
+    (fl--line-check-opens upward-objs)
+    (while (and upward-objs
+                (number-or-marker-p (fl--Open-close (car upward-objs)))
+                (< (fl--Open-close (car upward-objs))
+                   (point)))
+      (pop upward-objs))))
+
+(defsubst fl--set-closes (open-obj-list)
+  "Sets the close attribute of each element of OPEN-OBJ-LIST.
+
+OPEN-OBJ-LIST is a list of fl--Open. Each must be a child of the
+next in the list. This is used to scan-lists efficiently."
+  ;; Note: Because fl--Open-position values come from (nth 9
+  ;; (syntax-ppss)), we know they are not inside a string or comment.
+  ;; Thus buf-pos inits to a valid position to start scan-lists from.
+  (let ((buf-pos (and open-obj-list
+                      ;; scan_lists tolerates buf-pos past EOB
+                      (1+ (fl--Open-position (car open-obj-list))))))
+    (dolist (open-i open-obj-list)
+      (when buf-pos
+        (setq buf-pos (condition-case nil
+                          (scan-lists buf-pos 1 1)
+                        (scan-error nil))))
+      (setf (fl--Open-close open-i) (if buf-pos
+                                        (1- buf-pos)
+                                      'mismatched)))))
+
+(defun fl-propertize-region (start end)
+  (save-excursion
+    ;; In order to correctly remove faces from parens that changed
+    ;; from multiline to uniline, we clear all parens in the JIT lock
+    ;; region to start with.
+    (fl-unpropertize-region start end)
+    (let* ((timing-info (list (current-time)))
+           (start-ps (syntax-ppss start))
+           ;; Open positions, outer to inner
+           (ps-opens (nth 9 start-ps))
+           ;; fl--Open objects, positions inner to outer
+           (open-objs nil))
+      (push (current-time) timing-info)
+      ;; Process the broader region spanned by ps-opens. Consider only
+      ;; the ps-opens, not their children which lie entirely outside
+      ;; the JIT lock region.
+      ;;
+      ;; We mostly avoid further sexp parsing in the broader region,
+      ;; except to check for a multiline string just before setting
+      ;; inconsistent.
+      (dolist (ps-open-i ps-opens)
+        (push (make-fl--Open :position
+                             ps-open-i
+                             :column
+                             (progn
+                               (goto-char ps-open-i)
+                               (current-column)))
+              open-objs))
+      (push (current-time) timing-info)
+      ;; Filter out parens which don't need consideration outside the
+      ;; JIT lock region. The ones that do are currently fontified as
+      ;; inconsistent, and could become consistent if all its enclosed
+      ;; lines are checked.
+      ;;
+      ;; In addition to filtering, this passage sets close positions
+      ;; and may reapply the inconsistency-face to some close parens
+      ;; which were just cleared.
+      (setq open-objs
+            (let* ((objs-head (cons nil open-objs))
+                   (prev-open objs-head)
+                   (open-i (cdr objs-head))
+                   ;; Whether we've called fl--set-closes
+                   ;;
+                   ;; fl--set-closes is fairly expensive when near the
+                   ;; beginning of a long Lisp function. We can avoid
+                   ;; calling it if all open-objs are propertized as
+                   ;; consistent or mismatched.
+                   (closes-set nil))
+              (while open-i
+                (let* ((inconsistency-offset
+                        (get-text-property (fl--Open-position (car open-i))
+                                           'fl-inconsistency))
+                       (inconsistency-pos
+                        (and inconsistency-offset
+                             (+ (fl--Open-position (car open-i))
+                                inconsistency-offset))))
+                  (if (or (not inconsistency-pos)
+                          ;; Always nil so as "or" evaluation continues
+                          (unless closes-set
+                            ;; Lazy one-time call
+                            (fl--set-closes open-objs)
+                            (not (setq closes-set t)))
+                          ;; Spot check using the cached offset to
+                          ;; possibly avoid a complete check in
+                          ;; fl--region-check-opens for open-i.
+                          ;;
+                          ;; Because of buffer changes,
+                          ;; inconsistency-pos is not necessarily
+                          ;; the original. Just do a valid check.
+                          (and (< (fl--Open-position (car open-i))
+                                  inconsistency-pos)
+                               (number-or-marker-p (fl--Open-close (car 
open-i)))
+                               (<= inconsistency-pos
+                                   (fl--Open-close (car open-i)))
+                               (progn
+                                 (goto-char inconsistency-pos)
+                                 (fl--line-check-opens (list (car open-i)))
+                                 (when (fl--Open-inconsistent (car open-i))
+                                   (fl--colorize-inconsistent (car open-i))
+                                   t))))
+                      ;; Remove (car open-i) from list
+                      (setcdr prev-open (cdr open-i))
+                    (pop prev-open))
+                  (pop open-i)))
+              (cdr objs-head)))
+      (push (current-time) timing-info)
+      (when open-objs
+        ;; Check lists beginning before JIT lock's region (could
+        ;; scan to after JIT lock's region)
+        (let ((open-objs-reversed (reverse open-objs)))
+          (goto-char (fl--Open-position (car open-objs-reversed)))
+          (fl--region-check-opens open-objs-reversed
+                                  nil)))
+      (push (current-time) timing-info)
+      (goto-char start)
+      ;; Process within the inputted JIT lock region
+      (let* (;; Sparse vector of open paren data, indexed by position
+             ;; in buffer minus start. This benchmarked better than
+             ;; keeping a stack of fl--Open objects updated from the
+             ;; parse states of syntax-ppss.
+             (open-paren-table (make-vector (- end start) nil)))
+        (while (< (point) end)
+          (let ((indent-pos (progn (back-to-indentation)
+                                   (point)))
+                ;; Column at which text starts on the line
+                (indent-column (current-column))
+                (line-ppss (syntax-ppss))
+                (line-end (progn (end-of-line)
+                                 (point))))
+            ;; Skip whitespace only lines and lines beginning inside
+            ;; string
+            (unless (or (eq indent-pos line-end)
+                        (nth 3 line-ppss))
+              ;; Iterate over list of unclosed open parens
+              (dolist (open-pos (nth 9 line-ppss))
+                ;; Skip the already processed ones outside the region
+                (when (<= start open-pos)
+                  (let ((open-obj (or (aref open-paren-table
+                                            (- open-pos start))
+                                      (progn
+                                        (push (make-fl--Open
+                                               :position open-pos
+                                               :column (progn
+                                                         (goto-char open-pos)
+                                                         (current-column)))
+                                              open-objs)
+                                        (aset open-paren-table
+                                              (- open-pos start)
+                                              (car open-objs))))))
+                    (when (<= indent-column
+                              (fl--Open-column open-obj))
+                      (setf (fl--Open-inconsistent open-obj)
+                            (- indent-pos (fl--Open-position open-obj))))))))
+            ;; Go to next line. Since we already know line-end, use it
+            ;; instead of rescanning the line
+            (goto-char (1+ line-end))))
+        (push (current-time) timing-info)
+        ;; Process parens beginning in the JIT lock region but extending after
+        ;;
+        ;; Note: the reason we don't filter fl--Open after the JIT
+        ;; lock region, as we did for the region before it, is mostly
+        ;; because of the directionality of redisplay from BOB to EOB.
+        ;; If we allow subsequent fl-propertize-region to propertize
+        ;; the open parens in the current JIT lock region, it wouldn't
+        ;; show to the user because by then redisplay has finished
+        ;; this JIT lock region. An additional consideration is that
+        ;; the coloring of the open paren is of more interest than the
+        ;; close paren.
+        (let ((ps-opens (nth 9 (syntax-ppss end)))
+              ;; Inner to outer going towards the tail
+              (open-obj-list nil))
+          (dolist (ps-open-i ps-opens)
+            (when (<= start ps-open-i)
+              (push (or (aref open-paren-table
+                              (- ps-open-i start))
+                        ;; Open parens on the last line of the JIT
+                        ;; lock region don't have a fl--Open object
+                        ;; created yet.
+                        (progn
+                          (push (make-fl--Open
+                                 :position ps-open-i
+                                 :column (progn
+                                           (goto-char ps-open-i)
+                                           (current-column)))
+                                open-objs)
+                          (aset open-paren-table
+                                (- ps-open-i start)
+                                (car open-objs))))
+                    open-obj-list)))
+          (push (current-time) timing-info)
+          (fl--set-closes open-obj-list)
+          (push (current-time) timing-info)
+          (goto-char end)
+          (fl--region-check-opens nil open-obj-list))
+        (push (current-time) timing-info)
+        (dolist (open-i open-objs)
+          ;; Set close position
+          ;;
+          ;; Note: We do it here instead of when it was made so as
+          ;; some benefit from the fl--set-closes function's buffer
+          ;; scanning optimization. The lists processed here are
+          ;; opened and closed within JIT lock's region, so the less
+          ;; efficient buffer scanning is not a big deal.
+          (unless (fl--Open-close open-i)
+            (setf (fl--Open-close open-i)
+                  (condition-case nil
+                      (1- (scan-lists (fl--Open-position open-i) 1 0))
+                    (scan-error 'mismatched))))
+          ;; Apply the font color via text properties
+          (with-silent-modifications
+            (if (eq 'mismatched (fl--Open-close open-i))
+                (add-text-properties (fl--Open-position open-i)
+                                     (1+ (fl--Open-position open-i))
+                                     `(font-lock-face
+                                       fl-mismatched-face
+                                       rear-nonsticky
+                                       t))
+              (if (fl--Open-inconsistent open-i)
+                  (fl--colorize-inconsistent open-i)
+                (dolist (pos-i (list (fl--Open-position open-i)
+                                     (fl--Open-close open-i)))
+                  (remove-text-properties pos-i
+                                          (1+ pos-i)
+                                          '(fl-inconsistency
+                                            nil
+                                            font-lock-face
+                                            nil
+                                            rear-nonsticky
+                                            nil)))))))
+        (push (current-time) timing-info)
+        ;; (my-msg "fl-propertize-region start=%s end=%s timing: %s"
+        ;;         start end
+        ;;         (my-time-diffs (nreverse timing-info)))
+        ))))
+
+(defun fl-unpropertize-region (start end)
+  (goto-char start)
+  ;; remove-text-properties errors if (1+ (point)) is past EOB, so
+  ;; adjust end
+  (let ((end (min (1- (point-max))
+                  end)))
+    (while (< (point) end)
+      (skip-syntax-forward "^()" end)
+      (remove-text-properties (point)
+                              (1+ (point))
+                              '(fl-inconsistency nil
+                                font-lock-face nil
+                                rear-nonsticky nil))
+      (forward-char 1))))
+
+(defsubst flylisp-extend-region-after-change (start _end _old-len)
+  ;; It seems redisplay works its way from before start to after end,
+  ;; so it's more important to expand the start in order to get
+  ;; correct redisplays.
+  (save-excursion
+    (setq jit-lock-start
+          (or (syntax-ppss-toplevel-pos (syntax-ppss start))
+              start))))
+
+(define-minor-mode flylisp-mode
+  "Color unbalanced parentheses and parentheses inconsistent with
+  indentation."
+  nil nil nil
+  (if flylisp-mode
+      (progn
+        (jit-lock-register 'fl-propertize-region t)
+        (add-hook 'jit-lock-after-change-extend-region-functions
+                  #'flylisp-extend-region-after-change
+                  nil
+                  t))
+    (remove-hook 'jit-lock-after-change-extend-region-functions
+                 #'flylisp-extend-region-after-change
+                 t)
+    (jit-lock-unregister 'fl-propertize-region)
+    (save-excursion
+      (fl-unpropertize-region (point-min) (point-max)))))
+
+(provide 'flylisp)
+
+;;; flylisp.el ends here



reply via email to

[Prev in Thread] Current Thread [Next in Thread]