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

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

[elpa] master 36043c1 2/2: Merge commit '11843e2db4a24aaec2ad9a827ed4f07


From: Stephen Leake
Subject: [elpa] master 36043c1 2/2: Merge commit '11843e2db4a24aaec2ad9a827ed4f079588dcf58'
Date: Fri, 15 Feb 2019 10:48:35 -0500 (EST)

branch: master
commit 36043c1d768b309ba3471d5b3aa79b0520e0e34b
Merge: 9f5c4e0 11843e2
Author: Stephen Leake <address@hidden>
Commit: Stephen Leake <address@hidden>

    Merge commit '11843e2db4a24aaec2ad9a827ed4f079588dcf58'
---
 externals-list                  |   3 +-
 packages/el-search/NEWS         |  15 +
 packages/el-search/el-search.el | 676 +++++++++++++++++++++++++++-------------
 packages/sokoban/sokoban.el     |  99 ++++--
 4 files changed, 542 insertions(+), 251 deletions(-)

diff --git a/externals-list b/externals-list
index b2dd210..b6459a7 100644
--- a/externals-list
+++ b/externals-list
@@ -83,8 +83,7 @@
  ("gnome-c-style"      :subtree "https://github.com/ueno/gnome-c-style.git";)
  ("gnorb"               :subtree "https://github.com/girzel/gnorb";)
  ("gpastel"            :external 
"https://gitlab.petton.fr/DamienCassou/gpastel";)
- ;; FIXME: Waiting for copyright paperwork
- ;; ("greader"         :external 
"https://gitlab.com/michelangelo-rodriguez/greader";)
+ ("greader"            :external 
"https://gitlab.com/michelangelo-rodriguez/greader";)
  ("highlight-escape-sequences" :subtree 
"https://github.com/dgutov/highlight-escape-sequences/";)
  ("hyperbole"           :external 
"http://git.savannah.gnu.org/r/hyperbole.git";)
  ("ioccur"             :subtree 
"https://github.com/thierryvolpiatto/ioccur.git";)
diff --git a/packages/el-search/NEWS b/packages/el-search/NEWS
index e158df9..07bea82 100644
--- a/packages/el-search/NEWS
+++ b/packages/el-search/NEWS
@@ -1,6 +1,21 @@
 Some of the user visible news were:
 
 
+Version: 1.9.7
+
+  Changed default binding schemes: For reasons of harmonization, in
+  both searches and in el-search-occur both of basic keys s, r and n, p
+  now move to the next or previous match.
+
+  The default binding of 'el-search-continue-in-next-buffer' therefore
+  has been moved from n to x respectively.
+
+Version: 1.9.5
+
+  'string' and derived pattern types now support expressions evaluting
+  to regexps as arguments.  This means you can use 'rx' to construct
+  regexps in 'string' patterns, for example.
+
 Version: 1.9.0
 
   This version adds some help commands available through the C-h help
diff --git a/packages/el-search/el-search.el b/packages/el-search/el-search.el
index c85197c..b4981fe 100644
--- a/packages/el-search/el-search.el
+++ b/packages/el-search/el-search.el
@@ -7,7 +7,7 @@
 ;; Created: 29 Jul 2015
 ;; Keywords: lisp
 ;; Compatibility: GNU Emacs 25
-;; Version: 1.9.4
+;; Version: 1.9.7
 ;; Package-Requires: ((emacs "25") (stream "2.2.4") (cl-print "1.0"))
 
 
@@ -119,7 +119,7 @@
 ;;   C-O or M-RET (from a search pattern prompt)
 ;;     Execute this search command as occur.
 ;;
-;;   C-N, M-s e n (`el-search-continue-in-next-buffer')
+;;   C-X, M-s e x (`el-search-continue-in-next-buffer')
 ;;     Skip over current buffer or file.
 ;;
 ;;   C-D, M-s e d (`el-search-skip-directory')
@@ -249,7 +249,7 @@
 ;; `el-search-jump-to-search-head' (C-J; M-s e j): this command jumps
 ;; to the last match and re-activates the search.
 ;;
-;; `el-search-continue-in-next-buffer' (C-N; n) skips all remaining
+;; `el-search-continue-in-next-buffer' (C-X; x) skips all remaining
 ;; matches in the current buffer and continues searching in the next
 ;; buffer.  `el-search-skip-directory' (C-D; d) even skips all
 ;; subsequent files under a specified directory.
@@ -474,6 +474,21 @@
   "Expression based search and replace for Emacs Lisp."
   :group 'lisp)
 
+(defcustom el-search-display-mb-hints t
+  "Whether to show hints in the search pattern prompt."
+  :type 'boolean)
+
+(defcustom el-search-mb-hints-delay 0.8
+  "Time before displaying minibuffer hints.
+
+Setting this has only an effect if `el-search-display-mb-hints'
+is non-nil."
+  :type 'number)
+
+(defcustom el-search-mb-hints-timeout 15
+  "How long to display minibuffer hints."
+  :type 'number)
+
 (defface el-search-match '((((class color) (min-colors 88) (background dark))
                             (:background "#600000"))
                           (((class color) (min-colors 88) (background light))
@@ -788,11 +803,18 @@ nil."
          (unless ,done
            ,@unwindforms)))))
 
+(defvar el-search--last-message nil
+  "Internal var helping to avoid echo area stuttering ")
+
 (defun el-search--message-no-log (format-string &rest args)
   "Like `message' but with `message-log-max' bound to nil."
   (let ((message-log-max nil))
     (apply #'message format-string args)))
 
+(defun el-search--set-this-command-refresh-message-maybe ()
+  (when (eq (setq this-command 'el-search-pattern) last-command)
+    (message "%s" el-search--last-message)))
+
 (defalias 'el-search-read
   (if (boundp 'force-new-style-backquotes)
       (lambda (&optional stream)
@@ -863,10 +885,13 @@ nil."
               input)
             (symbol-value histvar)))))
 
+(defun el-search--pattern-is-unquoted-symbol-p (pattern)
+  (and (symbolp pattern)
+       (not (eq pattern '_))
+       (not (keywordp pattern))))
+
 (defun el-search--maybe-warn-about-unquoted-symbol (pattern)
-  (when (and (symbolp pattern)
-             (not (eq pattern '_))
-             (not (keywordp pattern)))
+  (when (el-search--pattern-is-unquoted-symbol-p pattern)
     (message "Free variable `%S' (missing a quote?)" pattern)
     (sit-for 2.)))
 
@@ -876,7 +901,115 @@ nil."
     (el-search--pushnew-to-history input histvar)
     (if (not (string= input "")) input (car (symbol-value histvar)))))
 
-(defun el-search-read-pattern-for-interactive (&optional prompt)
+(defvar el-search--display-match-count-in-prompt nil)
+(defvar el-search--mb-hints-timer nil)
+(defvar el-search--reading-input-for-query-replace nil)
+
+(defun el-search-read-pattern-trigger-mb-hints ()
+  (if (not (timerp el-search--mb-hints-timer))
+      (setq el-search--mb-hints-timer (run-at-time 3 nil 
#'el-search-read-display-mb-hints))
+    (timer-set-time el-search--mb-hints-timer (time-add (current-time) 
el-search-mb-hints-delay))
+    (timer-activate el-search--mb-hints-timer)))
+
+(defvar el-search--this-session-match-count-data nil)
+
+(defun el-search-read-pattern-setup-mb ()
+  ;; This is for minibuffer-setup-hook.
+  ;; Note: this doesn't care about stopping the
+  ;; 'el-search--mb-hints-timer'.
+  (when el-search-display-mb-hints
+    (setq el-search--this-session-match-count-data nil)
+    (when (timerp el-search--mb-hints-timer) (cancel-timer 
el-search--mb-hints-timer))
+    (setq el-search--mb-hints-timer nil)
+    (add-hook 'post-command-hook #'el-search-read-pattern-trigger-mb-hints t 
t)))
+
+(defvar el-search--search-pattern-1-do-fun nil)
+(defvar el-search--busy-animation
+  ;; '("." "o" "O" "o" "." " ")
+  ;; '("|" "/" "-" "\\")
+  '("*   " " *  " "  * " "   *" "  * " " *  "))
+(defvar el-search-mb-anim-time .33)
+
+(defun el-search--make-display-animation-function (display-fun)
+  (let ((last-update (seconds-to-time 0))
+        (anim (copy-sequence el-search--busy-animation)))
+    (setcdr (last anim) anim)
+    (lambda ()
+      (let ((now (current-time)))
+        (when (< el-search-mb-anim-time (float-time (time-subtract now 
last-update)))
+          (setq last-update now)
+          (funcall display-fun (pop anim)))))))
+
+(defun el-search-read-display-mb-hints ()
+  (when (minibufferp)
+    (while-no-input
+      (let (err)
+        (cl-macrolet ((try (&rest body)
+                           (let ((err-data (make-symbol "err-data")))
+                             `(condition-case ,err-data
+                                  (progn ,@body)
+                                (error (setq err ,err-data)
+                                       nil)))))
+          (let* ((input (minibuffer-contents))
+                 (pattern (pcase (ignore-errors (read-from-string input))
+                            (`(,expr . ,(or (guard 
el-search--reading-input-for-query-replace)
+                                            (pred (= (length input)))))
+                             expr)))
+                 (matcher (and pattern (try (el-search-make-matcher 
pattern)))))
+            (let* ((base-win (minibuffer-selected-window))
+                   (buf (window-buffer base-win)))
+              (if (and el-search--display-match-count-in-prompt matcher)
+                  (progn (with-current-buffer buf
+                           (setq el-search--current-search
+                                 (el-search-make-search
+                                  pattern
+                                  (let ((b (current-buffer)))
+                                    (lambda () (stream (list b)))))))
+                         (let ((ol (make-overlay (point-max) (point-max) nil t 
t)))
+                           (unwind-protect
+                               (cl-flet ((display-message
+                                          (lambda (message &rest args)
+                                            (setq message
+                                                  (propertize (apply #'format 
message args)
+                                                              'face 'shadow))
+                                            (put-text-property 0 1 'cursor t 
message)
+                                            (overlay-put ol 'after-string 
message)
+                                            (redisplay))))
+                                 (when 
(el-search--pattern-is-unquoted-symbol-p pattern)
+                                   ;; A very common mistake: input "foo" 
instead of "'foo"
+                                   (display-message
+                                    "    [Free variable `%S' (missing a 
quote?)]" pattern)
+                                   (sit-for 2))
+                                 (let ((el-search--search-pattern-1-do-fun
+                                        
(el-search--make-display-animation-function
+                                         (lambda (icon)
+                                           (display-message (concat "     " 
icon))))))
+                                   (display-message
+                                    "     %-12s"
+                                    (or (try (with-current-buffer buf
+                                               (cl-letf (((point) 
(window-point base-win)))
+                                                 
(el-search-display-match-count 'dont-message))))
+                                        (error-message-string err))))
+                                 (sit-for el-search-mb-hints-timeout))
+                             (delete-overlay ol))))
+                (unless (string= input "")
+                  (catch 'no-message
+                    (let ((minibuffer-message-timeout 
el-search-mb-hints-timeout))
+                      (minibuffer-message
+                       (propertize
+                        (format "    [%s]"
+                                (cond
+                                 ((not pattern) "invalid")
+                                 (err (error-message-string err))
+                                 (el-search--display-match-count-in-prompt "No 
match")
+                                 (t (throw 'no-message t))))
+                        'face 'shadow)))))))))))
+    (when quit-flag
+      ;; When `quit-flag' is bound here, it had been set by `while-no-input'
+      ;; meaning the user explicitly quit.  This means we must:
+      (funcall (key-binding [(control ?g)])))))
+
+(defun el-search-read-pattern-for-interactive (&optional prompt 
display-match-count)
   "Read an \"el-search\" pattern from the minibuffer, prompting with PROMPT.
 
 This function is designed to be used in the interactive form of
@@ -886,12 +1019,18 @@ from reading the pattern it also sets `this-command' to
 `el-search-pattern-history' and `el-search-query-replace-history'.
 
 PROMPT defaults to \"El-search pattern: \".  The return value is the
-`read' input pattern."
-  (let* ((input (el-search--read-pattern (or prompt "El-search pattern: ")
-                                         (car el-search-pattern-history)))
+`read' input pattern.
+
+With optional argument DISPLAY-MATCH-COUNT non-nil display a
+match count for the current buffer."
+  (let* ((input
+          (unwind-protect (minibuffer-with-setup-hook 
#'el-search-read-pattern-setup-mb
+                            (let ((el-search--display-match-count-in-prompt 
display-match-count))
+                              (el-search--read-pattern (or prompt "El-search 
pattern: ")
+                                                       (car 
el-search-pattern-history))))
+            (when (timerp el-search--mb-hints-timer)
+              (cancel-timer el-search--mb-hints-timer))))
          (pattern (el-search-read input)))
-    ;; A very common mistake: input "foo" instead of "'foo"
-    (el-search--maybe-warn-about-unquoted-symbol pattern)
     (setq this-command 'el-search-pattern) ;in case we come from isearch
     ;; Make input available also in query-replace history
     (el-search--pushnew-to-history input 'el-search-query-replace-history)
@@ -1154,6 +1293,8 @@ be specified as fourth argument, and COUNT becomes the 
fifth argument."
         (let ((match-beg nil) current-expr)
           (if (catch 'no-match
                 (while (not match-beg)
+                  (when el-search--search-pattern-1-do-fun
+                    (funcall el-search--search-pattern-1-do-fun))
                   (condition-case nil
                       (setq current-expr (el-search--ensure-sexp-start))
                     (end-of-buffer (throw 'no-match t)))
@@ -1654,7 +1795,7 @@ With ALLOW-LEADING-WHITESPACE non-nil, the match may
 be preceded by whitespace."
   (el-search--looking-at-1 (el-search-make-matcher pattern) 
allow-leading-whitespace))
 
-(defun el-search--all-matches (search)
+(defun el-search--all-matches (search &optional dont-copy)
   "Return a stream of all matches of SEARCH.
 The returned stream will always start searching from the
 beginning anew even when SEARCH has been used interactively or
@@ -1668,7 +1809,7 @@ The elements of the returned stream will have the form
 where BUFFER or FILE is the buffer or file where a match has been
 found (exactly one of the two will be nil), and MATCH-BEG is the
 position of the beginning of the match."
-  (let* ((search (el-search-reset-search (copy-el-search-object search)))
+  (let* ((search (if dont-copy search (el-search-reset-search 
(copy-el-search-object search))))
          (head (el-search-object-head search)))
     (seq-filter
      #'identity ;we use `nil' as a "skip" tag
@@ -1754,6 +1895,8 @@ in, in order, when called with no arguments."
 
     (keybind emacs-lisp-mode-map           ?s #'el-search-pattern)
     (keybind emacs-lisp-mode-map           ?r #'el-search-pattern-backward)
+    (keybind emacs-lisp-mode-map           ?n #'el-search-pattern)
+    (keybind emacs-lisp-mode-map           ?p #'el-search-pattern-backward)
     (keybind emacs-lisp-mode-map           ?% #'el-search-query-replace)
     (keybind emacs-lisp-mode-map           ?h #'el-search-this-sexp) ;h like 
in "highlight" or "here"
     (keybind global-map                    ?j #'el-search-jump-to-search-head)
@@ -1761,7 +1904,7 @@ in, in order, when called with no arguments."
     (keybind global-map                    ?< #'el-search-from-beginning)
     (keybind emacs-lisp-mode-map           ?> #'el-search-last-buffer-match)
     (keybind global-map                    ?d #'el-search-skip-directory)
-    (keybind global-map                    ?n 
#'el-search-continue-in-next-buffer)
+    (keybind global-map                    ?x 
#'el-search-continue-in-next-buffer)
 
     (keybind global-map                    ?o #'el-search-occur)
 
@@ -1806,7 +1949,7 @@ any case."
 Go back to the place where the search had been started."
   (interactive)
   (setq el-search--success nil)
-  (el-search-hl-post-command-fun) ;clear highlighting
+  (el-search-hl-post-command-fun 'stop) ;clear highlighting
   (let ((w (cadr el-search--search-origin)))
     (when (window-live-p w)
       (select-frame-set-input-focus (window-frame w))
@@ -2087,51 +2230,72 @@ Introduction to El-Search
 
 ;;;; Additional pattern type definitions
 
-(defun el-search-regexp-like-p (thing)
-  "Return non-nil when THING is regexp like.
+(defun el-search--simple-regexp-like-p (object)
+  (or (atom object)
+      (functionp object)
+      (and (consp object)
+           (if (fboundp 'proper-list-p) (proper-list-p object) t)
+           (not (consp (car object))))))
 
-In el-search, a regexp-like is either a normal regexp (i.e. a
-string), or a predicate accepting a string argument, or a list of
-the form
+(defun el-search-regexp-like-p (object)
+  "Return non-nil when OBJECT is regexp like.
 
-  \(bindings regexp\)
+In el-search, a regexp-like is either an expression evaluating to
+a normal regexp (e.g. a string or an `rx' form; it is evaluated
+once when a pattern is compiled) or a function accepting a string
+argument that can be used directly as a predicate for match
+testing, or a list of the form
 
-where REGEXP is the actual regexp to match and BINDINGS is a
-let-style list of variable bindings.
+  \(BINDINGS X\)
 
-Example: (((case-fold-search nil)) \"foo\") is a regexp like
-matching \"foo\", but not \"Foo\" even when `case-fold-search' is
-currently enabled."
-  (pcase thing
-    ((or (pred stringp) (pred functionp)) t)
+where BINDINGS is a let-style list of variable bindings and X one
+of the above.
+
+Example: (((case-fold-search nil)) (rx bos \"a\")) is a
+regexp-like matching any string starting with lower case \"a\"."
+  (pcase object
+    ((pred el-search--simple-regexp-like-p) t)
     (`(,(and (pred listp) bindings)
-       ,(pred stringp))
+       ,(pred el-search--simple-regexp-like-p))
      (cl-every
-      (lambda (binding) (pcase binding ((or (pred symbolp) `(,(pred symbolp)) 
`(,(pred symbolp) ,_)) t)))
+      (lambda (binding)
+        (pcase binding ((or (pred symbolp) `(,(pred symbolp)) `(,(pred 
symbolp) ,_)) t)))
       bindings))))
 
 (defun el-search--string-matcher (regexp-like)
   "Return a compiled match predicate for REGEXP-LIKE.
-That's a predicate returning non-nil when the
+This is a predicate returning non-nil when the
 `el-search-regexp-like-p' REGEXP-LIKE matches the (only)
 argument (that should be a string)."
-  (let ((match-bindings ()) regexp)
-    (pcase regexp-like
-      ((pred stringp) (setq regexp regexp-like))
-      (`(,binds ,real-regexp)
+  (let ((regexp) (match-bindings ()))
+    (pcase-exhaustive regexp-like
+      ((pred el-search--simple-regexp-like-p) (setq regexp regexp-like))
+      (`(,(and (pred listp) binds) ,real-regexp)
        (setq regexp real-regexp)
        (setq match-bindings binds)))
-    (if (functionp regexp-like)
-        (if (or (symbolp regexp-like) (byte-code-function-p regexp-like))
-            regexp-like
-          (byte-compile regexp-like))
+    (cl-flet ((wrap-let
+               (lambda (bindings body)
+                 (if (null bindings) body
+                   `(let ,bindings ,body)))))
       (byte-compile
        (let ((string (make-symbol "string")))
-         `(lambda (,string) (let ,match-bindings (string-match ,regexp 
,string))))))))
+         `(lambda (,string)
+            ,(wrap-let
+              match-bindings
+              (if (functionp regexp)
+                  `(funcall #',regexp ,string)
+                `(string-match
+                  ,(pcase (eval regexp t)
+                     ((and (pred stringp) s) s)
+                     (_ (error "Expression in regexp-like doesn't eval to a 
string: %S" regexp)))
+                  ,string)))))))))
 
 (el-search-defpattern string (&rest regexps)
   "Matches any string that is matched by all REGEXPS.
-Any of the REGEXPS is `el-search-regexp-like-p'."
+Any of the REGEXPS is `el-search-regexp-like-p'.
+
+If multiple REGEXPS are given, they don't need to match in order,
+so (string \"bar\" \"foo\") matches \"foobar\" for example."
   (declare (heuristic-matcher
             (lambda (&rest regexps)
               (let ((matchers (mapcar #'el-search--string-matcher regexps)))
@@ -2150,11 +2314,16 @@ Any of the REGEXPS is `el-search-regexp-like-p'."
   "Matches any symbol whose name is matched by all REGEXPS.
 Any of the REGEXPS is `el-search-regexp-like-p'.
 
+This pattern is equivalent to
+
+  `(and (pred symbolp)
+        (app symbol-name (string ,@regexps)))
+
 Example: to replace all symbols with names starting with \"foo-\"
 to start with \"bar-\" instead, you would use
 `el-search-query-replace' with a rule like this:
 
-  (and (symbol \"\\\\`foo-\\\\(.*\\\\)\") s) >
+  (and (symbol (rx bos \"foo-\" (group (+ nonl)))) s) >
   (intern (concat \"bar-\" (match-string 1 (symbol-name s))))"
   (declare (heuristic-matcher
             (lambda (&rest regexps)
@@ -2447,94 +2616,115 @@ absolute name must be matched by all of them."
   "Holds information for displaying a match count.
 The value is a list of elements
 
-   \(SEARCH BUFFER-CHARS-MOD-TICK BUFFER-MATCHES\)
-
-BUFFER-MATCHES is a stream of matches in this buffer.  SEARCH is
-the active search and BUFFER-CHARS-MOD-TICK the return value of
-`buffer-chars-modified-tick' from when this stream had been
-created.")
-
-(defun el-search-display-match-count ()
-  "Display an x/y-style match count in the echo area."
-  (when (and el-search--success (not el-search--wrap-flag))
-    (while-no-input
-
-      ;; Check whether cached stream of buffer matches is still valid
-      (pcase el-search--buffer-match-count-data
-        (`(,(pred (eq el-search--current-search))  ,(pred (eq 
(buffer-chars-modified-tick)))  . ,_))
-        (_
-         ;; (message "Refreshing match count data") (sit-for 1)
-         (redisplay) ;don't delay highlighting
-         (setq-local el-search--buffer-match-count-data
-                     (let ((stream-of-buffer-matches
-                            (seq-map #'cadr
-                                     (el-search--all-matches
-                                      (el-search-make-search
-                                       (el-search--current-pattern)
-                                       (let ((current-buffer (current-buffer)))
-                                         (lambda () (stream (list 
current-buffer)))))))))
-                       (list
-                        el-search--current-search
-                        (buffer-chars-modified-tick)
-                        stream-of-buffer-matches)))))
-
-      (let ((pos-here (point)) (matches-<=-here 1) total-matches
-            (defun-bounds (or (el-search--bounds-of-defun) (cons (point) 
(point))))
-            (matches-<=-here-in-defun 1) (total-matches-in-defun 0)
-            (largest-match-start-not-after-pos-here nil))
-        (pcase-let ((`(,_ ,_ ,matches) el-search--buffer-match-count-data))
-          (setq total-matches (let ((inhibit-message t)) (seq-length matches)))
-          (while (and (not (stream-empty-p matches)) (< (stream-first matches) 
(cdr defun-bounds)))
-            (when (<= (stream-first matches) pos-here)
-              (setq largest-match-start-not-after-pos-here (stream-first 
matches))
-              (unless (= (stream-first matches) pos-here)
-                (cl-incf matches-<=-here)))
-            (when (<= (car defun-bounds) (stream-first matches))
-              (cl-incf total-matches-in-defun)
-              (when (< (stream-first matches) pos-here)
-                (cl-incf matches-<=-here-in-defun)))
-            (stream-pop matches))
-          (if (zerop total-matches) ;this can happen for el-search-this-sexp
-              (el-search--message-no-log "No matches")
-            (let* ((at-a-match-but-not-at-match-beginning
-                    (and largest-match-start-not-after-pos-here
-                         (and (< largest-match-start-not-after-pos-here 
pos-here)
-                              (save-excursion
-                                (goto-char 
largest-match-start-not-after-pos-here)
-                                (<= pos-here (el-search--end-of-sexp))))))
-                   (at-a-match
-                    (and largest-match-start-not-after-pos-here
-                         (or (= pos-here 
largest-match-start-not-after-pos-here)
-                             at-a-match-but-not-at-match-beginning))))
-              (when (or at-a-match-but-not-at-match-beginning
-                        (not at-a-match))
-                (cl-decf matches-<=-here)
-                (cl-decf matches-<=-here-in-defun))
-              (if at-a-match
-                  (el-search--message-no-log
-                   "%s %d/%d  %s"
-                   (let ((head (el-search-object-head 
el-search--current-search)))
-                     (or (el-search-head-file head)
-                         (buffer-name (el-search-head-buffer head))))
-                   matches-<=-here
-                   total-matches
-                   (propertize
-                    (format (pcase (save-excursion
-                                     (goto-char (car defun-bounds))
-                                     (el-search-read (current-buffer)))
-                              (`(,a ,b . ,_) (format "(%s  %%d/%%d)"
-                                                     (truncate-string-to-width
-                                                      (format "%S %S" a b)
-                                                      40 nil nil 'ellipsis)))
-                              (_             "(%d/%d)"))
-                            matches-<=-here-in-defun total-matches-in-defun)
-                    'face 'shadow))
-                (el-search--message-no-log
-                 (concat "[Not at a match]   "
-                         (if (= matches-<=-here total-matches)
-                             (format "(%s/%s <-)" matches-<=-here 
total-matches)
-                           (format "(-> %s/%s)" (1+ matches-<=-here) 
total-matches))))))))))
-    (when quit-flag (el-search-keyboard-quit 'dont-quit))))
+  \(SEARCH BUFFER-CHARS-MOD-TICK (POINT-MIN POINT-MAX) MATCHES\)
+
+MATCHES is a stream of matches in this buffer.  The other values
+are used to check validity.")
+
+(defun el-search-display-match-count (&optional just-count)
+  "Display an x/y-style match count in the echo area.
+With optional argument JUST-COUNT non-nil, only return a string,
+don't display anything"
+  (when (or just-count (and el-search--success (not el-search--wrap-flag)))
+    (prog1
+        (while-no-input
+          (apply (if just-count #'format
+                   (lambda (&rest args)
+                     (setq el-search--last-message (apply 
#'el-search--message-no-log args))))
+                 (progn
+
+                   ;; Check whether cached stream of buffer matches is still 
valid
+                   (pcase el-search--buffer-match-count-data
+                     ((or
+                       (and `(,(and (pred el-search-object-p)
+                                    (pred (eq el-search--current-search)))
+                              . ,_)
+                            (pred (eq 
el-search--this-session-match-count-data)))
+                       `(,(pred (eq el-search--current-search))
+                         ,(pred (eq (buffer-chars-modified-tick)))
+                         (,(pred (eq (point-min))) ,(pred (eq (point-max))))  
. ,_)))
+
+                     (_
+                      ;; (message "Refreshing match count data") (sit-for 1)
+                      (redisplay) ;don't delay highlighting
+                      (setq-local el-search--buffer-match-count-data
+                                  (let ((stream-of-buffer-matches
+                                         (seq-map #'cadr
+                                                  (el-search--all-matches
+                                                   (el-search-make-search
+                                                    
(el-search--current-pattern)
+                                                    (let ((current-buffer 
(current-buffer)))
+                                                      (lambda () (stream (list 
current-buffer)))))
+                                                   'dont-copy))))
+                                    (list
+                                     el-search--current-search
+                                     (buffer-chars-modified-tick)
+                                     `(,(point-min) ,(point-max))
+                                     stream-of-buffer-matches)))
+                      (setq el-search--this-session-match-count-data
+                            el-search--buffer-match-count-data)))
+
+                   (let ((pos-here (point)) (matches-<=-here 1) total-matches
+                         (defun-bounds (or (el-search--bounds-of-defun) (cons 
(point) (point))))
+                         (matches-<=-here-in-defun 1) (total-matches-in-defun 
0)
+                         (largest-match-start-not-after-pos-here nil))
+                     (pcase-let ((`(,_ ,_ ,_ ,matches) 
el-search--buffer-match-count-data))
+                       (setq total-matches (let ((inhibit-message t)) 
(seq-length matches)))
+                       (while (and (not (stream-empty-p matches)) (< 
(stream-first matches) (cdr defun-bounds)))
+                         (when (<= (stream-first matches) pos-here)
+                           (setq largest-match-start-not-after-pos-here 
(stream-first matches))
+                           (unless (= (stream-first matches) pos-here)
+                             (cl-incf matches-<=-here)))
+                         (when (<= (car defun-bounds) (stream-first matches))
+                           (cl-incf total-matches-in-defun)
+                           (when (< (stream-first matches) pos-here)
+                             (cl-incf matches-<=-here-in-defun)))
+                         (stream-pop matches))
+                       (if (zerop total-matches)
+                           (list "(No matches)")
+                         (let* ((at-a-match-but-not-at-match-beginning
+                                 (and largest-match-start-not-after-pos-here
+                                      (and (< 
largest-match-start-not-after-pos-here pos-here)
+                                           (save-excursion
+                                             (goto-char 
largest-match-start-not-after-pos-here)
+                                             (<= pos-here 
(el-search--end-of-sexp))))))
+                                (at-a-match
+                                 (and largest-match-start-not-after-pos-here
+                                      (or (= pos-here 
largest-match-start-not-after-pos-here)
+                                          
at-a-match-but-not-at-match-beginning))))
+                           (when (or at-a-match-but-not-at-match-beginning
+                                     (not at-a-match))
+                             (cl-decf matches-<=-here)
+                             (cl-decf matches-<=-here-in-defun))
+                           (if at-a-match
+                               (let ((buffer-or-file
+                                      (let ((head (el-search-object-head 
el-search--current-search)))
+                                        (or (el-search-head-file head)
+                                            (buffer-name 
(el-search-head-buffer head))))))
+                                 (if just-count
+                                     (list "%d/%d" matches-<=-here 
total-matches)
+                                   (list
+                                    "%s %d/%d  %s"
+                                    buffer-or-file
+                                    matches-<=-here
+                                    total-matches
+                                    (propertize
+                                     (format (pcase (save-excursion
+                                                      (goto-char (car 
defun-bounds))
+                                                      (el-search-read 
(current-buffer)))
+                                               (`(,a ,b . ,_) (format "(%s  
%%d/%%d)"
+                                                                      
(truncate-string-to-width
+                                                                       (format 
"%S %S" a b)
+                                                                       40 nil 
nil 'ellipsis)))
+                                               (_             "(%d/%d)"))
+                                             matches-<=-here-in-defun 
total-matches-in-defun)
+                                     'face 'shadow))))
+                             (list
+                              (concat (if (not just-count) "[Not at a match]   
" "")
+                                      (if (= matches-<=-here total-matches)
+                                          (format "(%s/%s <-)" matches-<=-here 
total-matches)
+                                        (format "(-> %s/%s)" (1+ 
matches-<=-here) total-matches))))))))))))
+      (when quit-flag (el-search-keyboard-quit 'dont-quit)))))
 
 (defun el-search-hl-other-matches (matcher)
   "Highlight all visible matches.
@@ -2567,18 +2757,47 @@ local binding of `window-scroll-functions'."
   (setq el-search-hl-other-overlays '())
   (el-search-rehide-invisible))
 
-(defun el-search-hl-post-command-fun ()
-  (pcase this-command
-    ('el-search-query-replace)
-    ((guard (el-search--entering-prefix-arg-p))) ; don't hide key input 
feedback
-    ('el-search-pattern (el-search-display-match-count))
-    ((pred el-search-keep-session-command-p))
-    (_ (unless el-search-keep-hl
-         (el-search-hl-remove)
-         (remove-hook 'post-command-hook 'el-search-hl-post-command-fun t)
-         (setq el-search--temp-buffer-flag nil)
-         (el-search-kill-left-over-search-buffers)
-         (el-search-close-quick-help-maybe)))))
+(defvar el-search-hl-post-command-fun--last-animator nil)
+
+(defun el-search-hl-post-command-fun (&optional stop)
+  "Do cleanup when last search has obviously been terminated.
+
+If a search is active, arrange to count matches in the background
+and show a match count when done.
+
+With argument STOP non-nil, force cleanup."
+  (cl-flet ((stop (lambda ()
+                    (el-search-hl-remove)
+                    (remove-hook 'post-command-hook 
'el-search-hl-post-command-fun t)
+                    (setq el-search--temp-buffer-flag nil)
+                    (el-search-kill-left-over-search-buffers)
+                    (el-search-close-quick-help-maybe)
+                    (setq el-search--this-session-match-count-data nil))))
+    (pcase this-command
+      ((guard stop) (stop))
+      ('el-search-query-replace)
+      ((guard (el-search--entering-prefix-arg-p))) ; don't hide key input 
feedback
+      ('el-search-pattern
+       (let ((el-search--search-pattern-1-do-fun
+              (if (eq this-command last-command)
+                  el-search-hl-post-command-fun--last-animator
+                (setq el-search-hl-post-command-fun--last-animator
+                      (el-search--make-display-animation-function
+                       (lambda (icon)
+                         (let ((inhibit-message nil))
+                           (setq el-search--last-message
+                                 (el-search--message-no-log
+                                  "%s   %s"
+                                  (let ((head (el-search-object-head 
el-search--current-search)))
+                                    (or (el-search-head-file head)
+                                        (el-search-head-buffer head)))
+                                  icon)))))))))
+         (condition-case err (el-search-display-match-count)
+           (error
+            (el-search--message-no-log
+             "Error counting matches: %s" (error-message-string err))))))
+      ((pred el-search-keep-session-command-p))
+      (_ (unless el-search-keep-hl (stop))))))
 
 (defun el-search--pending-search-p ()
   (memq #'el-search-hl-post-command-fun post-command-hook))
@@ -2700,7 +2919,7 @@ make current."
                      (if (numberp arg) arg 1)))))
              (when (and (numberp arg) (not match-pos))
                (setq el-search--success nil)
-               (el-search-hl-post-command-fun)
+               (el-search-hl-post-command-fun 'stop)
                (goto-char (car el-search--search-origin))
                (user-error "No match there"))
              (unless (or (numberp arg) (eq (point) match-pos))
@@ -2732,81 +2951,83 @@ be the current buffer, and the search will be resumed 
from point
 instead of the position where the search would normally be
 continued."
   (interactive "P")
-  (setq this-command 'el-search-pattern)
+  (el-search--set-this-command-refresh-message-maybe)
   (unless (eq last-command this-command)
     (el-search--set-search-origin-maybe)
     (el-search-compile-pattern-in-search el-search--current-search))
   (el-search-protect-search-head
-   (unwind-protect
-       (let* ((old-current-buffer (current-buffer))
-              (head (el-search-object-head el-search--current-search))
-              (current-search-buffer
-               (or (el-search-head-buffer head)
-                   (el-search--next-buffer el-search--current-search))))
-         (when from-here
-           (cond
-            ((eq (current-buffer) current-search-buffer)
-             (setf (el-search-head-position head) (copy-marker (point))))
-            ((and current-search-buffer (buffer-live-p current-search-buffer))
-             (user-error "Please resume from buffer %s" (buffer-name 
current-search-buffer)))
-            (current-search-buffer
-             (user-error "Search head points to a killed buffer"))))
-         (let ((match nil)
-               (matcher (el-search--current-matcher))
-               (heuristic-matcher (el-search--current-heuristic-matcher)))
-           (while (and (el-search-head-buffer head)
-                       (not (setq match (with-current-buffer 
(el-search-head-buffer head)
-                                          (save-excursion
-                                            (goto-char 
(el-search-head-position head))
-                                            (el-search--search-pattern-1
-                                             matcher t nil 
heuristic-matcher))))))
-             (el-search--next-buffer el-search--current-search))
-           (if (not match)
-               (progn
-                 (if (not (or el-search--success
-                              (and from-here
-                                   (save-excursion
-                                     (goto-char (point-min))
-                                     (el-search--search-pattern-1 matcher t 
nil heuristic-matcher)))))
-                     (progn
-                       (el-search--message-no-log "No matches")
-                       (sit-for .7))
-                   (el-search--set-wrap-flag 'forward)
-                   (let ((keys (car (where-is-internal 'el-search-pattern))))
-                     (el-search--message-no-log
-                      (if keys
-                          (format "No (more) matches - Hit %s to wrap search"
-                                  (key-description keys))
-                        "No (more) matches")))))
-             (let (match-start)
-               ;; If (el-search-head-buffer head) is only a worker buffer, 
replace it
-               ;; with a buffer created with `find-file-noselect'
-               (with-current-buffer (el-search-head-buffer head)
-                 (goto-char match)
-                 (setq match-start (point))
-                 (when el-search--temp-file-buffer-flag
-                   (let ((file-name buffer-file-name))
-                     (setq buffer-file-name nil) ;prevent f-f-ns to find this 
buffer
-                     (let ((buffer-list-before (buffer-list))
-                           (new-buffer (find-file-noselect file-name)))
-                       (setf (el-search-head-buffer head) new-buffer)
-                       (unless (memq new-buffer buffer-list-before)
-                         (with-current-buffer new-buffer
-                           (setq-local el-search--temp-buffer-flag t)))))))
-               (pop-to-buffer (el-search-head-buffer head) 
el-search-display-next-buffer-action)
-               (goto-char match-start))
-             (setf (el-search-object-last-match el-search--current-search)
-                   (copy-marker (point)))
-             (setf (el-search-head-position head)
-                   (copy-marker (point)))
-             (el-search-hl-sexp)
-             (unless (and (eq this-command last-command)
-                          el-search--success
-                          (eq (current-buffer) old-current-buffer))
-               (el-search-hl-other-matches matcher))
-             (setq el-search--success t)))
-         (el-search-prefix-key-maybe-set-transient-map))
-     (el-search-kill-left-over-search-buffers))))
+   (el-search-when-unwind
+       (unwind-protect
+           (let* ((old-current-buffer (current-buffer))
+                  (head (el-search-object-head el-search--current-search))
+                  (current-search-buffer
+                   (or (el-search-head-buffer head)
+                       (el-search--next-buffer el-search--current-search))))
+             (when from-here
+               (cond
+                ((eq (current-buffer) current-search-buffer)
+                 (setf (el-search-head-position head) (copy-marker (point))))
+                ((and current-search-buffer (buffer-live-p 
current-search-buffer))
+                 (user-error "Please resume from buffer %s" (buffer-name 
current-search-buffer)))
+                (current-search-buffer
+                 (user-error "Search head points to a killed buffer"))))
+             (let ((match nil)
+                   (matcher (el-search--current-matcher))
+                   (heuristic-matcher (el-search--current-heuristic-matcher)))
+               (while (and (el-search-head-buffer head)
+                           (not (setq match (with-current-buffer 
(el-search-head-buffer head)
+                                              (save-excursion
+                                                (goto-char 
(el-search-head-position head))
+                                                (el-search--search-pattern-1
+                                                 matcher t nil 
heuristic-matcher))))))
+                 (el-search--next-buffer el-search--current-search))
+               (if (not match)
+                   (progn
+                     (if (not (or el-search--success
+                                  (and from-here
+                                       (save-excursion
+                                         (goto-char (point-min))
+                                         (el-search--search-pattern-1 matcher 
t nil heuristic-matcher)))))
+                         (progn
+                           (el-search--message-no-log "No matches")
+                           (sit-for .7))
+                       (el-search--set-wrap-flag 'forward)
+                       (let ((keys (car (where-is-internal 
'el-search-pattern))))
+                         (el-search--message-no-log
+                          (if keys
+                              (format "No (more) matches - Hit %s to wrap 
search"
+                                      (key-description keys))
+                            "No (more) matches")))))
+                 (let (match-start)
+                   ;; If (el-search-head-buffer head) is only a worker buffer, 
replace it
+                   ;; with a buffer created with `find-file-noselect'
+                   (with-current-buffer (el-search-head-buffer head)
+                     (goto-char match)
+                     (setq match-start (point))
+                     (when el-search--temp-file-buffer-flag
+                       (let ((file-name buffer-file-name))
+                         (setq buffer-file-name nil) ;prevent f-f-ns to find 
this buffer
+                         (let ((buffer-list-before (buffer-list))
+                               (new-buffer (find-file-noselect file-name)))
+                           (setf (el-search-head-buffer head) new-buffer)
+                           (unless (memq new-buffer buffer-list-before)
+                             (with-current-buffer new-buffer
+                               (setq-local el-search--temp-buffer-flag t)))))))
+                   (pop-to-buffer (el-search-head-buffer head) 
el-search-display-next-buffer-action)
+                   (goto-char match-start))
+                 (setf (el-search-object-last-match el-search--current-search)
+                       (copy-marker (point)))
+                 (setf (el-search-head-position head)
+                       (copy-marker (point)))
+                 (el-search-hl-sexp)
+                 (unless (and (eq this-command last-command)
+                              el-search--success
+                              (eq (current-buffer) old-current-buffer))
+                   (el-search-hl-other-matches matcher))
+                 (setq el-search--success t)))
+             (el-search-prefix-key-maybe-set-transient-map))
+         (el-search-kill-left-over-search-buffers))
+     (el-search-hl-post-command-fun 'stop))))
 
 (defun el-search-skip-directory (directory)
   "Skip all subsequent matches in files located under DIRECTORY."
@@ -2827,14 +3048,14 @@ continued."
          (string-match-p "\\`\\.\\." (file-relative-name buffer-or-file-name 
directory)))))
   (el-search-prefix-key-maybe-set-transient-map))
 
-(defun el-search-pattern--interactive (&optional prompt)
+(defun el-search-pattern--interactive (&optional prompt display-match-count)
   (list (if (or
              ;;Hack to make a pop-up buffer search from occur "stay active"
              (el-search--pending-search-p)
              (and (eq this-command last-command)
                   (or el-search--success el-search--wrap-flag)))
             (el-search--current-pattern)
-          (el-search-read-pattern-for-interactive prompt))))
+          (el-search-read-pattern-for-interactive prompt 
display-match-count))))
 
 ;;;###autoload
 (defun el-search-pattern (pattern)
@@ -2858,7 +3079,7 @@ types defined with `el-search-defpattern'.
 
 See `el-search-defined-patterns' for a list of defined patterns."
   (declare (interactive-only el-search-forward))
-  (interactive (el-search-pattern--interactive))
+  (interactive (el-search-pattern--interactive nil 'display-match-count))
   (cond
    ((eq el-search--wrap-flag 'forward)
     (progn
@@ -3028,7 +3249,7 @@ direction.  See `el-search-forward' for details."
   "Search the current buffer backward for matches of PATTERN.
 See the command `el-search-pattern' for more information."
   (declare (interactive-only el-search-backward))
-  (interactive (el-search-pattern--interactive))
+  (interactive (el-search-pattern--interactive nil 'display-match-count))
   (if (eq pattern (el-search--current-pattern))
       (progn
         (el-search-compile-pattern-in-search el-search--current-search)
@@ -3042,7 +3263,7 @@ See the command `el-search-pattern' for more information."
     ;; Make this buffer the current search buffer so that a following C-S
     ;; doesn't delete highlighting
     (el-search--next-buffer el-search--current-search))
-  (setq this-command 'el-search-pattern)
+  (el-search--set-this-command-refresh-message-maybe)
   (when (eq el-search--wrap-flag 'backward)
     (el-search--set-wrap-flag nil)
     (el-search--message-no-log "[Wrapped backward search]")
@@ -3120,7 +3341,7 @@ Use the normal search commands to seize the search."
   "Jump to the first match starting after `window-end'."
   (interactive)
   (el-search-barf-if-not-search-buffer)
-  (setq this-command 'el-search-pattern)
+  (el-search--set-this-command-refresh-message-maybe)
   (let ((here (point)))
     (goto-char (window-end))
     (if (el-search--search-pattern-1 (el-search--current-matcher) t nil
@@ -3134,7 +3355,7 @@ Use the normal search commands to seize the search."
   "Jump to the hindmost match starting before `window-start'."
   (interactive)
   (el-search-barf-if-not-search-buffer)
-  (setq this-command 'el-search-pattern)
+  (el-search--set-this-command-refresh-message-maybe)
   (let ((here (point)))
     (goto-char (window-start))
     (if (el-search--search-backward-1 (el-search--current-matcher) t nil
@@ -3322,6 +3543,8 @@ Prompt for a new pattern and revert."
     (define-key map [(shift tab)]   #'el-search-occur-cycle)
     (define-key map [?p]            #'el-search-occur-previous-match)
     (define-key map [?n]            #'el-search-occur-next-match)
+    (define-key map [?r]            #'el-search-occur-previous-match)
+    (define-key map [?s]            #'el-search-occur-next-match)
     (define-key map [?e]            #'el-search-edit-occur-pattern)
     (define-key map [?c ?n]         #'el-search-occur-no-context)
     (define-key map [?c ?d]         #'el-search-occur-defun-context)
@@ -4370,8 +4593,13 @@ Don't save this buffer and all following buffers; don't 
ask again"))))
                                                (el-search-read (car 
el-search-query-replace-history)))
                                         (car el-search-query-replace-history)
                                       (car el-search-pattern-history))))))
-                      (el-search--read-pattern "Query replace pattern: " nil
-                                               
'el-search-query-replace-history)))
+                      ;; We only want error hints so we don't bind 
el-search--display-match-count-in-prompt
+                      (unwind-protect (minibuffer-with-setup-hook 
#'el-search-read-pattern-setup-mb
+                                        (let 
((el-search--reading-input-for-query-replace t))
+                                          (el-search--read-pattern "Query 
replace pattern: " nil
+                                                                   
'el-search-query-replace-history)))
+                        (when (timerp el-search--mb-hints-timer)
+                          (cancel-timer el-search--mb-hints-timer)))))
         from to read-from read-to)
     (with-temp-buffer
       (emacs-lisp-mode)
diff --git a/packages/sokoban/sokoban.el b/packages/sokoban/sokoban.el
index 4698450..128d59a 100644
--- a/packages/sokoban/sokoban.el
+++ b/packages/sokoban/sokoban.el
@@ -1,11 +1,13 @@
-;;; sokoban.el --- Implementation of Sokoban for Emacs.
+;;; sokoban.el --- Implementation of Sokoban for Emacs. -*- lexical-binding: t 
-*-
 
-;; Copyright (C) 1998, 2013, 2017 Free Software Foundation, Inc.
+;; Copyright (C) 1998, 2013, 2017, 2019 Free Software Foundation, Inc.
 
 ;; Author: Glynn Clements <address@hidden>
 ;; Maintainer: Dieter Deyke <address@hidden>
-;; Version: 1.4.6
-;; Package-Requires: ((emacs "23.1"))
+;; Version: 1.4.8
+;; Comment: While we set lexical-binding, it currently doesn't make use
+;;          of closures, which is why it can still work in Emacs-23.1.
+;; Package-Requires: ((emacs "23.1") (cl-lib "0.5"))
 ;; Created: 1997-09-11
 ;; Keywords: games
 ;; Package-Type: multi
@@ -52,8 +54,7 @@
 
 ;;; Code:
 
-(eval-when-compile
-  (require 'cl))
+(eval-when-compile (require 'cl-lib))
 
 (require 'gamegrid)
 (require 'xml)
@@ -508,6 +509,8 @@ static char * player_on_target_xpm[] = {
     (define-key map "r"        'sokoban-restart-level)
     (define-key map "g"        'sokoban-goto-level)
     (define-key map "F"        'fit-frame-to-buffer)
+    (define-key map "s"        'sokoban-save)
+    (define-key map "l"        'sokoban-load)
 
     (define-key map [left]     'sokoban-move-left)
     (define-key map [right]    'sokoban-move-right)
@@ -536,7 +539,7 @@ static char * player_on_target_xpm[] = {
     (dolist (SokobanLevels tree)
       (dolist (LevelCollection (xml-get-children SokobanLevels 
'LevelCollection))
         (dolist (Level (xml-get-children LevelCollection 'Level))
-          (incf n)
+          (cl-incf n)
           (insert (format ";LEVEL %d\n" n))
           (dolist (L (xml-get-children Level 'L))
             (insert (car (xml-node-children L)))
@@ -561,7 +564,7 @@ static char * player_on_target_xpm[] = {
         (setq r 0)
         (while (not (or (eobp)
                        (looking-at sokoban-comment-regexp)))
-          (incf r)
+          (cl-incf r)
           (setq sokoban-height (max sokoban-height r)
                 sokoban-width (max sokoban-width (- (line-end-position) 
(line-beginning-position))))
          (forward-line))))
@@ -626,10 +629,10 @@ static char * player_on_target_xpm[] = {
        (cond
         ((or (eq c sokoban-target)
              (eq c sokoban-player-on-target))
-         (incf sokoban-targets))
+         (cl-incf sokoban-targets))
         ((eq c sokoban-block-on-target)
-         (incf sokoban-targets)
-         (incf sokoban-done))
+         (cl-incf sokoban-targets)
+         (cl-incf sokoban-done))
         ((= c ?\040) ;; treat space characters in level file as floor
          (aset (aref sokoban-level-map y) x sokoban-floor)))))))
 
@@ -650,14 +653,14 @@ static char * player_on_target_xpm[] = {
   (let ((y sokoban-score-y))
     (dolist (string (list (format "Moves:  %05d" sokoban-moves)
                          (format "Pushes: %05d" sokoban-pushes)
-                         (format "Done:   %d/%d"
+                         (format "Done:   %d/%d "
                                  sokoban-done
                                  sokoban-targets)))
       (let* ((len (length string)))
         (dotimes (x len)
          (gamegrid-set-cell (+ sokoban-score-x x)
                             y (aref string x))))
-      (incf y)))
+      (cl-incf y)))
   (setq mode-line-format
        (format "Sokoban:   Level: %d/%d   Moves: %05d   Pushes: %05d   Done: 
%d/%d"
                sokoban-level (length sokoban-level-data) sokoban-moves 
sokoban-pushes
@@ -666,13 +669,13 @@ static char * player_on_target_xpm[] = {
 
 (defun sokoban-add-move (dx dy)
   (push (list 'move dx dy) sokoban-undo-list)
-  (incf sokoban-moves)
+  (cl-incf sokoban-moves)
   (sokoban-draw-score))
 
 (defun sokoban-add-push (dx dy)
   (push (list 'push dx dy) sokoban-undo-list)
-  (incf sokoban-moves)
-  (incf sokoban-pushes)
+  (cl-incf sokoban-moves)
+  (cl-incf sokoban-pushes)
   (sokoban-draw-score))
 
 (defun sokoban-targetp (x y)
@@ -714,21 +717,21 @@ static char * player_on_target_xpm[] = {
                    (y (+ sokoban-y dy)))
               (sokoban-set-floor x y)
               (if (sokoban-targetp x y)
-                  (decf sokoban-done))
+                  (cl-decf sokoban-done))
               (sokoban-set-block sokoban-x sokoban-y)
               (if (sokoban-targetp sokoban-x sokoban-y)
-                  (incf sokoban-done)))
+                  (cl-incf sokoban-done)))
             (setq sokoban-x (- sokoban-x dx))
             (setq sokoban-y (- sokoban-y dy))
             (sokoban-set-player sokoban-x sokoban-y)
-            (decf sokoban-pushes)
-            (decf sokoban-moves))
+            (cl-decf sokoban-pushes)
+            (cl-decf sokoban-moves))
            ((eq type 'move)
             (sokoban-set-floor sokoban-x sokoban-y)
             (setq sokoban-x (- sokoban-x dx))
             (setq sokoban-y (- sokoban-y dy))
             (sokoban-set-player sokoban-x sokoban-y)
-            (decf sokoban-moves))
+            (cl-decf sokoban-moves))
            (t
             (message "Invalid entry in sokoban-undo-list")))
       (sokoban-draw-score))))
@@ -752,14 +755,14 @@ static char * player_on_target_xpm[] = {
             (cond ((or (eq cc sokoban-floor)
                        (eq cc sokoban-target))
                    (if (sokoban-targetp x y)
-                       (decf sokoban-done))
+                       (cl-decf sokoban-done))
                     (sokoban-set-block xx yy)
                    (sokoban-set-player x y)
                    (sokoban-set-floor sokoban-x sokoban-y)
                    (setq sokoban-x x
                          sokoban-y y)
                    (if (sokoban-targetp xx yy)
-                       (incf sokoban-done))
+                       (cl-incf sokoban-done))
                    (sokoban-add-push dx dy)
                    (cond ((= sokoban-done sokoban-targets)
                            (let ((level sokoban-level))
@@ -867,14 +870,58 @@ static char * player_on_target_xpm[] = {
   (setq sokoban-level 0)
   (sokoban-next-level))
 
-(put 'sokoban-mode 'mode-class 'special)
+(defvar sokoban-grid-state)
+
+(defconst sokoban-state-variables '(
+                                    sokoban-level
+                                    sokoban-level-map
+                                    sokoban-targets
+                                    sokoban-x
+                                    sokoban-y
+                                    sokoban-moves
+                                    sokoban-pushes
+                                    sokoban-done
+                                    sokoban-undo-list
+                                    sokoban-grid-state
+                                    ))
+(defun sokoban-save (filename)
+  "Save current Sokoban state."
+  (interactive "FSave file: ")
+  (let ((buf (current-buffer)))
+    (setq sokoban-grid-state nil)
+    (dotimes (y sokoban-height)
+      (dotimes (x sokoban-width)
+        (push (gamegrid-get-cell x y) sokoban-grid-state)))
+    (setq sokoban-grid-state (reverse sokoban-grid-state))
+    (with-temp-file filename
+      (dolist (var sokoban-state-variables)
+        (print
+         (with-current-buffer buf (eval var))
+         (current-buffer))))))
+
+(defun sokoban-load (filename)
+  "Restore saved Sokoban state."
+  (interactive "fLoad file: ")
+  (let ((buf (current-buffer)))
+    (with-temp-buffer
+      (insert-file-contents filename)
+      (goto-char (point-min))
+      (dolist (var sokoban-state-variables)
+        (let ((value (read (current-buffer))))
+          (with-current-buffer buf (set var value))))))
+  (dotimes (y sokoban-height)
+    (dotimes (x sokoban-width)
+      (gamegrid-set-cell x y (pop sokoban-grid-state))))
+  (sokoban-draw-score))
 
 (easy-menu-define sokoban-popup-menu nil "Popup menu for Sokoban mode."
   '("Sokoban Commands"
     ["Restart this level" sokoban-restart-level]
     ["Start new game" sokoban-start-game]
     ["Go to specific level" sokoban-goto-level]
-    ["Fit frame to buffer" fit-frame-to-buffer]))
+    ["Fit frame to buffer" fit-frame-to-buffer]
+    ["Save current state" sokoban-save]
+    ["Restore saved state" sokoban-load]))
 (define-key sokoban-mode-map [down-mouse-3] sokoban-popup-menu)
 
 (define-derived-mode sokoban-mode special-mode "Sokoban"
@@ -904,6 +951,8 @@ sokoban-mode keybindings:
 \\[sokoban-restart-level]      Restarts the current level
 \\[sokoban-goto-level] Jumps to a specified level
 \\[fit-frame-to-buffer]        Fit frame to buffer
+\\[sokoban-save]       Save current state
+\\[sokoban-load]       Restore saved state
 \\[sokoban-move-left]  Move one square to the left
 \\[sokoban-move-right] Move one square to the right
 \\[sokoban-move-up]    Move one square up



reply via email to

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