commit 55a200d7071320311365787b60f311c7c91922d8 (HEAD, refs/remotes/origin/master) Author: Dmitry Gutov Date: Sun Apr 14 04:22:14 2024 +0300 ; Capitalize 'project' in project-name's docstring diff --git a/lisp/progmodes/project.el b/lisp/progmodes/project.el index da211566a3b..000a05804a8 100644 --- a/lisp/progmodes/project.el +++ b/lisp/progmodes/project.el @@ -291,7 +291,7 @@ headers search path, load path, class path, and so on." nil) (cl-defgeneric project-name (project) - "A human-readable name for the project. + "A human-readable name for the PROJECT. Nominally unique, but not enforced." (file-name-nondirectory (directory-file-name (project-root project)))) commit e8c6e3fa477e69b4cecfee354af313ccb60e8c97 Author: Po Lu Date: Mon Apr 15 09:21:17 2024 +0800 Fix bug#70385 * src/xdisp.c (note_fringe_highlight): Don't proceed if popup_activated, window is outdated, or when row beneath pointer does not display text. (bug#70385) diff --git a/src/xdisp.c b/src/xdisp.c index 452adee1d31..d984c12d1aa 100644 --- a/src/xdisp.c +++ b/src/xdisp.c @@ -35731,16 +35731,28 @@ note_mode_line_or_margin_highlight (Lisp_Object window, int x, int y, /* Take proper action when mouse has moved to the window WINDOW, with - window-local x-position X and y-position Y. This is only used for + window-local x-position X and y-position Y. This is only used for displaying user-defined fringe indicator help-echo messages. */ static void -note_fringe_highlight (Lisp_Object window, int x, int y, +note_fringe_highlight (struct frame *f, Lisp_Object window, int x, int y, enum window_part part) { - if (!NILP (help_echo_string)) + if (!NILP (help_echo_string) || !f->glyphs_initialized_p) return; + /* When a menu is active, don't highlight because this looks odd. */ +#if defined (HAVE_X_WINDOWS) || defined (HAVE_NS) || defined (MSDOS) \ + || defined (HAVE_ANDROID) + if (popup_activated ()) + return; +#endif /* HAVE_X_WINDOWS || HAVE_NS || MSDOS || HAVE_ANDROID */ + +#if defined HAVE_HAIKU + if (popup_activated_p) + return; +#endif /* HAVE_HAIKU */ + /* Find a message to display through the help-echo mechanism whenever the mouse hovers over a fringe indicator. Both text properties and overlays have to be checked. */ @@ -35757,6 +35769,13 @@ note_fringe_highlight (Lisp_Object window, int x, int y, struct window *w = XWINDOW (window); x_y_to_hpos_vpos (w, x, y, &hpos, &vpos, 0, 0, &area); + /* Don't access the TEXT_AREA of a row that does not display text, or + when the window is outdated. (bug#70385) */ + if (window_outdated (w) + || !MATRIX_ROW_DISPLAYS_TEXT_P (MATRIX_ROW (w->current_matrix, + vpos))) + return; + /* Get to the first glyph of a text row based on the vertical position of the fringe. */ struct glyph *glyph = MATRIX_ROW_GLYPH_START (w->current_matrix, vpos); @@ -36014,7 +36033,7 @@ note_mouse_highlight (struct frame *f, int x, int y) else if (part == ON_LEFT_FRINGE || part == ON_RIGHT_FRINGE) { cursor = FRAME_OUTPUT_DATA (f)->nontext_cursor; - note_fringe_highlight (window, x, y, part); + note_fringe_highlight (f, window, x, y, part); } else if (part == ON_VERTICAL_SCROLL_BAR || part == ON_HORIZONTAL_SCROLL_BAR) commit 3d3602055264ca3095b7f28ca7e27a6f2782649a Author: Mattias Engdegård Date: Sun Apr 14 18:20:47 2024 +0200 GC-mark temporary key values created when sorting (bug#69709) Bug reported and fix proposed by Aris Spathis. * src/sort.c (merge_markmem): Mark heap-allocated temporary key values. (tim_sort): Delay key function calls to after marking function has been registered. * test/src/fns-tests.el (fns-tests-sort-gc): New test. diff --git a/src/sort.c b/src/sort.c index 527d5550342..808cd187dcf 100644 --- a/src/sort.c +++ b/src/sort.c @@ -532,6 +532,9 @@ merge_markmem (void *arg) merge_state *ms = arg; eassume (ms != NULL); + if (ms->allocated_keys != NULL) + mark_objects (ms->allocated_keys, ms->listlen); + if (ms->reloc.size != NULL && *ms->reloc.size > 0) { Lisp_Object *src = (ms->reloc.src->values @@ -1107,21 +1110,29 @@ tim_sort (Lisp_Object predicate, Lisp_Object keyfunc, if (length < MERGESTATE_TEMP_SIZE / 2) keys = &ms.temparray[length + 1]; else - keys = allocated_keys = xmalloc (length * word_size); - - for (ptrdiff_t i = 0; i < length; i++) - keys[i] = call1 (keyfunc, seq[i]); + { + /* Fill with valid Lisp values in case a GC occurs before all + keys have been computed. */ + verify (NIL_IS_ZERO); + keys = allocated_keys = xzalloc (length * word_size); + } lo.keys = keys; lo.values = seq; } + merge_init (&ms, length, allocated_keys, &lo, predicate); + + /* Compute keys after merge_markmem has been registered by merge_init + (any call to keyfunc might trigger a GC). */ + if (!NILP (keyfunc)) + for (ptrdiff_t i = 0; i < length; i++) + keys[i] = call1 (keyfunc, seq[i]); + /* FIXME: This is where we would check the keys for interesting properties for more optimised comparison (such as all being fixnums etc). */ - merge_init (&ms, length, allocated_keys, &lo, predicate); - /* March over the array once, left to right, finding natural runs, and extending short natural runs to minrun elements. */ const ptrdiff_t minrun = merge_compute_minrun (length); diff --git a/test/src/fns-tests.el b/test/src/fns-tests.el index 1b13785a9fc..5ba7e49324a 100644 --- a/test/src/fns-tests.el +++ b/test/src/fns-tests.el @@ -418,6 +418,27 @@ (should-not (and (> size 0) (eq res seq))) (should (equal seq input)))))))))))) +(ert-deftest fns-tests-sort-gc () + ;; Make sure our temporary storage is traversed by the GC. + (let* ((n 1000) + (a (mapcar #'number-to-string (number-sequence 1 n))) + (i 0) + ;; Force frequent GCs in both the :key and :lessp functions. + (s (sort a + :key (lambda (x) + (setq i (1+ i)) + (when (> i 300) + (garbage-collect) + (setq i 0)) + (copy-sequence x)) + :lessp (lambda (a b) + (setq i (1+ i)) + (when (> i 300) + (garbage-collect) + (setq i 0)) + (string< a b))))) + (should (equal (length s) (length a))))) + (defvar w32-collate-ignore-punctuation) (ert-deftest fns-tests-collate-sort () commit 568c1741352a4932508fbbd474b9fd9ebe90ddfb Author: Juri Linkov Date: Sun Apr 14 19:18:31 2024 +0300 Add 'forward-sexp-default-function' to be used by 'treesit-forward-sexp' * lisp/emacs-lisp/lisp.el (forward-sexp-default-function): New function with body from 'forward-sexp' (bug#68993). (forward-sexp-function): Change the default value from nil to 'forward-sexp-default-function'. (forward-sexp): Use either 'forward-sexp-function' or 'forward-sexp-default-function'. * lisp/treesit.el (treesit-forward-sexp): In nodes of type 'text' fall back to 'forward-sexp-default-function'. Improve docstring. * doc/lispref/positions.texi (List Motion): Fix pxref. diff --git a/doc/lispref/positions.texi b/doc/lispref/positions.texi index 5e0143c7131..9193c1063d1 100644 --- a/doc/lispref/positions.texi +++ b/doc/lispref/positions.texi @@ -892,8 +892,8 @@ parser information to move across syntax constructs. Since what exactly is considered a sexp varies between languages, a major mode should set @code{treesit-thing-settings} to determine that. Then the mode can get navigation-by-sexp functionality for free, by using -@code{forward-sexp} and @code{backward-sexp}(@pxref{Moving by -Sentences,,, emacs, The extensible self-documenting text editor}). +@code{forward-sexp} and @code{backward-sexp}(@pxref{Expressions, +,, emacs, The extensible self-documenting text editor}). @node Skipping Characters @subsection Skipping Characters diff --git a/etc/NEWS b/etc/NEWS index bc8be557711..99f33a7b8dd 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -2103,6 +2103,10 @@ All tree-sitter enabled modes that define 'sentence' in ** Functions and variables to move by program sexps +*** New function 'forward-sexp-default-function'. +The previous implementation of 'forward-sexp' is moved into its +own function, to be bound by 'forward-sexp-function'. + *** New function 'treesit-forward-sexp'. Tree-sitter conditionally sets 'forward-sexp-function' for major modes that have defined 'sexp' in 'treesit-thing-settings' to enable diff --git a/lisp/emacs-lisp/lisp.el b/lisp/emacs-lisp/lisp.el index c57b1357f63..bd0b38db7ea 100644 --- a/lisp/emacs-lisp/lisp.el +++ b/lisp/emacs-lisp/lisp.el @@ -45,7 +45,12 @@ This affects `insert-parentheses' and `insert-pair'." :type 'boolean :group 'lisp) -(defvar forward-sexp-function nil +(defun forward-sexp-default-function (&optional arg) + "Default function for `forward-sexp-function'." + (goto-char (or (scan-sexps (point) arg) (buffer-end arg))) + (if (< arg 0) (backward-prefix-chars))) + +(defvar forward-sexp-function #'forward-sexp-default-function ;; FIXME: ;; - for some uses, we may want a "sexp-only" version, which only ;; jumps over a well-formed sexp, rather than some dwimish thing @@ -74,10 +79,9 @@ report errors as appropriate for this kind of usage." "No next sexp" "No previous sexp")))) (or arg (setq arg 1)) - (if forward-sexp-function - (funcall forward-sexp-function arg) - (goto-char (or (scan-sexps (point) arg) (buffer-end arg))) - (if (< arg 0) (backward-prefix-chars))))) + (funcall (or forward-sexp-function + #'forward-sexp-default-function) + arg))) (defun backward-sexp (&optional arg interactive) "Move backward across one balanced expression (sexp). diff --git a/lisp/treesit.el b/lisp/treesit.el index 1443162f79c..2973aba771c 100644 --- a/lisp/treesit.el +++ b/lisp/treesit.el @@ -2138,14 +2138,24 @@ however, smaller in scope than sentences. This is used by (defun treesit-forward-sexp (&optional arg) "Tree-sitter implementation for `forward-sexp-function'. -ARG is described in the docstring of `forward-sexp-function'. If -there are no further sexps to move across, signal `scan-error' -like `forward-sexp' does. If point is already at top-level, -return nil without moving point." +ARG is described in the docstring of `forward-sexp-function'. + +If point is inside a text environment where tree-sitter is not +supported, go forward a sexp using `forward-sexp-default-function'. +If point is inside code, use tree-sitter functions with the +following behavior. If there are no further sexps to move across, +signal `scan-error' like `forward-sexp' does. If point is already +at top-level, return nil without moving point. + +What constitutes as text and source code sexp is determined +by `text' and `sexp' in `treesit-thing-settings'." (interactive "^p") (let ((arg (or arg 1)) (pred (or treesit-sexp-type-regexp 'sexp))) - (or (if (> arg 0) + (or (when (treesit-node-match-p (treesit-node-at (point)) 'text t) + (funcall #'forward-sexp-default-function arg) + t) + (if (> arg 0) (treesit-end-of-thing pred (abs arg) 'restricted) (treesit-beginning-of-thing pred (abs arg) 'restricted)) ;; If we couldn't move, we should signal an error and report commit cd113d8c45ccf3bfa8b687c06a5d03618adf7a2c Author: Stefan Monnier Date: Sun Apr 14 12:07:23 2024 -0400 text.texi (Tracking changes): Fix warning * doc/lispref/text.texi (Change Hooks): Add a menu to silence warnings. (Tracking changes): Improve the title. diff --git a/doc/lispref/text.texi b/doc/lispref/text.texi index 0cd4e2c614e..07fb730f0f1 100644 --- a/doc/lispref/text.texi +++ b/doc/lispref/text.texi @@ -6386,8 +6386,12 @@ use @code{combine-change-calls} or @code{combine-after-change-calls} instead. @end defvar +@menu +* Tracking changes:: Keeping track of buffer modifications. +@end menu + @node Tracking changes -@subsection Tracking changes +@subsection Keeping track of buffer modifications @cindex track-changes @cindex change tracker commit 85ece8b494429b5ae36e79d7b4ad85a993f73543 Author: Mattias Engdegård Date: Sun Apr 14 12:47:43 2024 +0200 ; * src/eval.c (funcall_lambda): Sink specpdl load out of fast path. diff --git a/src/eval.c b/src/eval.c index f48d7b0682f..7f7a70b15ae 100644 --- a/src/eval.c +++ b/src/eval.c @@ -3158,13 +3158,9 @@ apply_lambda (Lisp_Object fun, Lisp_Object args, specpdl_ref count) or a module function. */ static Lisp_Object -funcall_lambda (Lisp_Object fun, ptrdiff_t nargs, - register Lisp_Object *arg_vector) +funcall_lambda (Lisp_Object fun, ptrdiff_t nargs, Lisp_Object *arg_vector) { - Lisp_Object val, syms_left, next, lexenv; - specpdl_ref count = SPECPDL_INDEX (); - ptrdiff_t i; - bool optional, rest; + Lisp_Object syms_left, lexenv; if (CONSP (fun)) { @@ -3211,13 +3207,16 @@ funcall_lambda (Lisp_Object fun, ptrdiff_t nargs, else emacs_abort (); - i = optional = rest = 0; + specpdl_ref count = SPECPDL_INDEX (); + ptrdiff_t i = 0; + bool optional = false; + bool rest = false; bool previous_rest = false; for (; CONSP (syms_left); syms_left = XCDR (syms_left)) { maybe_quit (); - next = XCAR (syms_left); + Lisp_Object next = XCAR (syms_left); if (!SYMBOLP (next)) xsignal1 (Qinvalid_function, fun); @@ -3269,6 +3268,7 @@ funcall_lambda (Lisp_Object fun, ptrdiff_t nargs, /* Instantiate a new lexical environment. */ specbind (Qinternal_interpreter_environment, lexenv); + Lisp_Object val; if (CONSP (fun)) val = Fprogn (XCDR (XCDR (fun))); else if (SUBR_NATIVE_COMPILEDP (fun)) commit 616af565796f8c690dd9c7d1b2fa7607f2e2fa1e Author: Mattias Engdegård Date: Sat Apr 13 17:43:34 2024 +0200 ; * lisp/emacs-lisp/macroexp.el (macroexp--expand-all): less consing diff --git a/lisp/emacs-lisp/macroexp.el b/lisp/emacs-lisp/macroexp.el index b87b749dd76..bb4797cac8b 100644 --- a/lisp/emacs-lisp/macroexp.el +++ b/lisp/emacs-lisp/macroexp.el @@ -351,7 +351,7 @@ Assumes the caller has bound `macroexpand-all-environment'." (let ((default-tail nil) (n 0) (rest clauses)) - (while rest + (while (cdr rest) (let ((c (car-safe (car rest)))) (when (cond ((consp c) (and (memq (car c) '(quote function)) (cadr c))) commit 7add47337b62064998a5b80f357acc39b1253e98 Author: Earl Hyatt Date: Sun Mar 24 11:49:21 2024 -0400 Add command 'list-keyboard-macros' that works like 'list-buffers'. The command 'list-keyboard-macros' allows editing and re-arranging macros using 'tabulated-list-mode'. Existing keyboard macros can be duplicated or deleted. Macro counters and counter formats can take new values read from the minibuffer. Macro keys can be edited using 'edit-kbd-macro'. * doc/emacs/kmacro.texi (Kmacro Menu): Document the new command and the menu's commands. * etc/NEWS (Kmacro Menu Mode): Mention the new mode and command. * lisp/kmacro.el (kmacro-menu-mark, kmacro-menu-marked) (kmacro-menu-flagged): Add faces for marks and flags. * lisp/kmacro.el (kmacro-menu-mode-map, kmacro-menu-mode): Add mode and map. * lisp/kmacro.el (list-keyboard-macros, kmacro-menu): Add command. * lisp/kmacro.el (kmacro-menu--deletion-flags, kmacro-menu--marks) (kmacro-menu--id-kmacro, kmacro-menu--id-position, kmacro-menu--kmacros) (kmacro-menu--refresh, kmacro-menu--map-ids, kmacro-menu--replace-all) (kmacro-menu--replace-at, kmacro-menu--query-revert, kmacro-menu--assert-row) (kmacro-menu--propertize-keys, kmacro-menu--do-region) (kmacro-menu--marks-exist-p): Add utility functions of mode and commands. * lisp/kmacro.el (kmacro-menu-mark, kmacro-menu-flag-for-deletion) (kmacro-menu-unmark, kmacro-menu-unmark-backward) (kmacro-menu-unmark-all): Add commands for marks and flags. * lisp/kmacro.el (kmacro-menu-do-flagged-delete, kmacro-menu-do-copy) (kmacro-menu-do-delete): Add commands that modify the ring. * lisp/kmacro.el (kmacro-menu-edit-position, kmacro-menu-transpose) (kmacro-menu-edit-format, kmacro-menu-edit-counter) (kmacro-menu-edit-keys, kmacro-menu-edit-column): Add commands that modify a keyboard macro. diff --git a/doc/emacs/kmacro.texi b/doc/emacs/kmacro.texi index e30def34475..4a8d4d4f093 100644 --- a/doc/emacs/kmacro.texi +++ b/doc/emacs/kmacro.texi @@ -42,6 +42,8 @@ intelligent or general. For such things, Lisp must be used. * Edit Keyboard Macro:: Editing keyboard macros. * Keyboard Macro Step-Edit:: Interactively executing and editing a keyboard macro. +* Kmacro Menu:: An interface for listing and editing + keyboard macros and the keyboard macro ring. @end menu @node Basic Keyboard Macro @@ -616,3 +618,163 @@ including the final @kbd{C-j}), and appends them at the end of the keyboard macro; it then terminates the step-editing and replaces the original keyboard macro with the edited macro. @end itemize + +@node Kmacro Menu +@section Listing and Editing Keyboard Macros +@cindex Kmacro Menu + +@cindex listing current keyboard macros +@kindex M-x list-keyboard-macros @key{RET} +@findex kmacro-menu +@findex list-keyboard-macros + To display a list of existing keyboard macros, type @kbd{M-x +list-keyboard-macros @key{RET}}. This pops up the @dfn{Kmacro Menu} in +a buffer named @file{*Keyboard Macro List*}. Each line in the list +shows one macro's position, counter value, counter format, that counter +value using that format, and macro keys. Here is an example of a macro +list: + +@smallexample +Position Counter Format Formatted Keys +0 8 %02d 08 N : SPC RET +1 0 %d 0 l o n g SPC p h r a s e +@end smallexample + +@noindent +The macros are listed with the current macro at the top in position +number zero and the older macros in the order in which they are found in +the keyboard macro ring (@pxref{Keyboard Macro Ring}). Using the Kmacro +Menu, you can change the order of the macros and change their counters, +counter formats, and keys. The Kmacro Menu is a read-only buffer, and +can be changed only through the special commands described in this +section. After a command is run, the Kmacro Menu displays changes to +reflect the new values of the macro properties and the macro ring. You +can use the usual cursor motion commands in this buffer, as well as +special motion commands for navigating the table. To view a list of the +special commands, type @kbd{C-h m} or @kbd{?} (@code{describe-mode}) in +the Kmacro Menu. + + You can use the following commands to change a macro's properties: + +@table @kbd +@item # +@findex kmacro-menu-edit-position +@kindex # @r{(Kmacro Menu)} +Change the position of the macro on the current line +(@pxref{Keyboard Macro Ring}). + +@item C-x C-t +@findex kmacro-menu-transpose +@kindex C-x C-t @r{(Kmacro Menu)} +Move the macro on the current line to the line above, like in +@code{transpose-lines}. + +@item c +@findex kmacro-menu-edit-counter +@kindex c @r{(Kmacro Menu)} +Change the counter value of the macro on the current line +(@pxref{Keyboard Macro Counter}). + +@item f +@findex kmacro-menu-edit-format +@kindex f @r{(Kmacro Menu)} +Change the counter format of the macro on the current line. + +@item e +@findex kmacro-menu-edit-keys +@kindex e @r{(Kmacro Menu)} +Change the keys of the macro on the current line using +@code{edit-kbd-macro} (@pxref{Edit Keyboard Macro}). + +@item @key{RET} +@findex kmacro-menu-edit-column +@kindex @key{RET} @r{(Kmacro Menu)} +Change the value in the current column of the macro on the current line +using commands above. +@end table + + The following commands delete or duplicate macros in the list: + +@table @kbd +@item d +@findex kmacro-menu-flag-for-deletion +@item d @r{(Kmacro Menu)} +Flag the macro on the current line for deletion, then move point to the +next line (@code{kmacro-menu-flag-for-deletion}). The deletion flag is +indicated by the character @samp{D} at the start of line. The deletion +occurs only when you type the @kbd{x} command (see below). + + If the region is active, this command flags all of the macros in the +region. + +@item x +@findex kmacro-menu-do-flagged-delete +@item x @r{(Kmacro Menu)} +Delete the macros in the list that have been flagged for deletion +(@code{kmacro-menu-do-flagged-delete}). + +@item m +@findex kmacro-menu-mark +@item m @r{(Kmacro Menu)} +Mark the macro on the current line, then move point to the next line +(@code{kmacro-menu-mark}). Marked macros are indicated by the character +@samp{*} at the start of line. Marked macros can be operated on by the +@kbd{C} and @kbd{D} commands (see below). + + If the region is active, this command marks all of the macros in the +region. + +@item C +@findex kmacro-menu-do-copy +@item C @r{(Kmacro Menu)} +This command copies macros by duplicating them at their current +positions in the list (@code{kmacro-menu-do-copy}). For example, +running this command on the macro at position number zero will insert a +copy of that macro into position number one and move the remaining +macros down. + + If the region is active, this command duplicates the macros in the +region. Otherwise, if there are marked macros, this command duplicates +the marked macros. If there is no region nor are there marked macros, +this command duplicates the macro on the current line. In the first two +cases, the command prompts for confirmation before duplication. + +@item D +@findex kmacro-menu-do-delete +@item D @r{(Kmacro Menu)} +This command deletes macros, removing them from the ring +(@code{kmacro-menu-do-delete}). For example, running this command on +the macro at position number zero will delete the current macro and then +make the first macro in the macro ring (previously at position number +one) the new current macro, popping it from the ring. + + If the region is active, this command deletes the macros in the +region. Otherwise, if there are marked macros, this command deletes the +marked macros. If there is no region nor are there marked macros, this +command deletes the macro on the current line. In all cases, the +command prompts for confirmation before deletion. + + This command is an alternative to the @kbd{d} and @kbd{x} commands +(see above). + +@item u +@findex kmacro-menu-unmark +@item u @r{(Kmacro Menu)} +Unmark and unflag the macro on the current line, then move point down +to the next line (@code{kmacro-menu-unmark}). If there is an active +region, this command unmarks and unflags all of the macros in the +region. + +@item @key{DEL} +@findex kmacro-menu-unmark-backward +@item @key{DEL} @r{(Kmacro Menu)} +Like the @kbd{u} command (see above), but move point up to the previous +line when there is no active region +(@code{kmacro-menu-unmark-backward}). + +@item U +@findex kmacro-menu-unmark-all +@item U @r{(Kmacro Menu)} +Unmark and unflag all macros in the list +(@code{kmacro-menu-unmark-all}). +@end table diff --git a/etc/NEWS b/etc/NEWS index 7a73815179c..bc8be557711 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -1510,6 +1510,16 @@ macros with many lines, such as from 'kmacro-edit-lossage'. The user option 'proced-auto-update-flag' can now be set to 2 additional values, which control automatic updates of Proced buffers that are not displayed in some window. +** Kmacro Menu Mode + ++++ +*** New mode 'kmacro-menu-mode' and new command 'list-keyboard-macros'. +The new command 'list-keyboard-macros' is the keyboard-macro version +of commands like 'list-buffers' and 'list-processes', creating a listing +of the currently existing keyboards macros using the new mode +'kmacro-menu-mode'. It allows rearranging the macros in the ring, +duplicating them, deleting them, and editing their counters, formats, +and keys. ** Miscellaneous diff --git a/lisp/kmacro.el b/lisp/kmacro.el index 897ebf14330..a16f70105c1 100644 --- a/lisp/kmacro.el +++ b/lisp/kmacro.el @@ -1388,6 +1388,564 @@ To customize possible responses, change the \"bindings\" in (let ((executing-kbd-macro nil)) (redisplay)))) +;;; Mode and commands for working with the ring in a table + +(defface kmacro-menu-mark '((t (:inherit font-lock-constant-face))) + "Face used for the Keyboard Macro Menu marks." + :group 'kmacro + :version "30.1") + +(defface kmacro-menu-flagged '((t (:inherit error))) + "Face used for keyboard macros flagged for deletion." + :group 'kmacro + :version "30.1") + +(defface kmacro-menu-marked '((t (:inherit warning))) + "Face used for keyboard macros marked for duplication." + :group 'kmacro + :version "30.1") + +(defvar-keymap kmacro-menu-mode-map + :doc "Keymap for `kmacro-menu-mode'." + :parent tabulated-list-mode-map + "#" #'kmacro-menu-edit-position + "c" #'kmacro-menu-edit-counter + "e" #'kmacro-menu-edit-keys + "f" #'kmacro-menu-edit-format + "RET" #'kmacro-menu-edit-column + + "C" #'kmacro-menu-do-copy + "D" #'kmacro-menu-do-delete + "m" #'kmacro-menu-mark + + "d" #'kmacro-menu-flag-for-deletion + "x" #'kmacro-menu-do-flagged-delete + + "u" #'kmacro-menu-unmark + "U" #'kmacro-menu-unmark-all + "DEL"#'kmacro-menu-unmark-backward + + " " #'kmacro-menu-transpose) + +(define-derived-mode kmacro-menu-mode tabulated-list-mode + "Keyboard Macro Menu" + "Major mode for listing and editing keyboard macros." + (make-local-variable 'kmacro-menu--marks) + (make-local-variable 'kmacro-menu--deletion-flags) + (setq-local tabulated-list-format + [("Position" 8 nil) + ("Counter" 8 nil :right-align t :pad-right 2) + ("Format" 8 nil) + ("Formatted" 10 nil) + ("Keys" 1 nil)]) + (setq-local tabulated-list-padding 2) + (add-hook 'tabulated-list-revert-hook #'kmacro-menu--refresh nil t) + (tabulated-list-init-header) + (unless (kmacro-ring-empty-p) + (kmacro-menu--refresh) + (tabulated-list-print))) + +;;;###autoload +(defalias 'kmacro-menu #'list-keyboard-macros) +;;;###autoload +(defun list-keyboard-macros () + "List the keyboard macros." + (interactive) + (let ((buf (get-buffer-create "*Keyboard Macro List*"))) + (with-current-buffer buf + (kmacro-menu-mode)) + (pop-to-buffer buf))) + +;;;; Utility functions and mode data + +(defvar kmacro-menu--deletion-flags nil + "Alist of entries flagged for deletion.") + +(defvar kmacro-menu--marks nil + "Alist of entries marked for copying and duplication.") + +(defun kmacro-menu--id-kmacro (entry-id) + "Return the keyboard macro that is part of the ENTRY-ID." + (car entry-id)) + +(defun kmacro-menu--id-position (entry-id) + "Return the ordinal position that is part of the ENTRY-ID." + (cdr entry-id)) + +(defun kmacro-menu--kmacros () + "Return the list of the existing keyboard macros or nil, if none are defined." + (when last-kbd-macro + (cons (kmacro-ring-head) + kmacro-ring))) + +(defun kmacro-menu--refresh () + "Reset the list of keyboard macros." + (setq-local tabulated-list-entries + (seq-map-indexed (lambda (km idx) + (let ((cnt (kmacro--counter km)) + (fmt (kmacro--format km))) + `((,km . ,idx) + [,(format "%d" idx) + ,(format "%d" cnt) + ,fmt + ,(format fmt cnt) + ,(format-kbd-macro (kmacro--keys km))]))) + (kmacro-menu--kmacros)) + kmacro-menu--deletion-flags nil + kmacro-menu--marks nil) + (tabulated-list-clear-all-tags)) + +(defun kmacro-menu--map-ids (function) + "Apply FUNCTION to the current table's entry IDs in order. + +Return a list of the output of FUNCTION." + (mapcar function + (mapcar #'car + (seq-sort-by #'cdar #'< tabulated-list-entries)))) + +(defun kmacro-menu--replace-all (kmacros) + "Replace the existing keyboard macros with those in KMACROS. + +The first element in the list overwrites the values of `last-kbd-macro', +`kmacro-counter', and `kmacro-counter-format'. The remaining elements +become the value of `kmacro-ring'. + +KMACROS is a list of `kmacro' objects." + (if (null kmacros) + (setq last-kbd-macro nil + kmacro-counter-format kmacro-default-counter-format + kmacro-counter 0 + kmacro-ring nil) + (if (not (seq-every-p #'kmacro-p kmacros)) + (error "All elements must satisfy `kmacro-p'") + (kmacro-split-ring-element (car kmacros)) + (setq kmacro-ring (cdr kmacros))))) + +(defun kmacro-menu--replace-at (kmacro n) + "Replace the keyboard macro at position N with KMACRO. + +This function replaces all of the existing keyboard macros via +`kmacro-menu--replace-all'. Except for the macro at position N, which will +be KMACRO, the replacement macros are the existing macros identified in +the table." + (kmacro-menu--replace-all + (kmacro-menu--map-ids (lambda (id) + (if (= n (kmacro-menu--id-position id)) + kmacro + (kmacro-menu--id-kmacro id)))))) + +(defun kmacro-menu--query-revert () + "If the table differs from the existing macros, ask whether to revert table." + (when (and (not (equal (kmacro-menu--kmacros) + (kmacro-menu--map-ids #'kmacro-menu--id-kmacro))) + (yes-or-no-p "Table does not match existing keyboard macros. Stop and revert table?")) + (tabulated-list-revert) + (signal 'quit nil))) + +(defun kmacro-menu--assert-row (&optional id) + "Signal an error if point is not on a table row. + +ID is the tabulated list id of the supposed entry at point." + (unless (or id (tabulated-list-get-id)) + (user-error "Not on a table row"))) + +(defun kmacro-menu--propertize-keys (face) + "Redisplay the macro keys on the current line with FACE." + (tabulated-list-set-col 4 (propertize (aref (tabulated-list-get-entry) 4) + 'face face))) + +(defun kmacro-menu--do-region (function) + "Run FUNCTION on macros in the region or on the current line at the line start. + +If there is an active region, for each line in the region, move to the +beginning of the line and apply FUNCTION to the table entry ID of the +line. If there is no region, apply FUNCTION only to the table entry ID +of the current line. + +When there is no active region, advance to the beginning of the next +line after applying FUNCTION." + (if (use-region-p) + (save-excursion + (let* ((reg-beg (region-beginning)) + (reg-end (region-end)) + (line-beg (progn + (goto-char reg-beg) + (pos-bol))) + (line-end (progn + (goto-char reg-end) + (if (bolp) + reg-end + (pos-bol 2))))) + (goto-char line-beg) + (let ((id)) + (while (and (< (point) line-end) + (setq id (tabulated-list-get-id))) + (kmacro-menu--assert-row id) + (funcall function id) + (forward-line 1))))) + (let ((id (tabulated-list-get-id))) + (kmacro-menu--assert-row id) + (goto-char (pos-bol)) + (funcall function id) + (forward-line 1)))) + +(defun kmacro-menu--marks-exist-p () + "Return non-nil if markers exist for any table entries." + (let ((tag (gensym))) + (catch tag + (kmacro-menu--map-ids (lambda (id) + (when (alist-get (kmacro-menu--id-position id) + kmacro-menu--marks) + (throw tag t)))) + nil))) + +;;;; Commands for Marks and Flags + +(defun kmacro-menu-mark () + "Mark macros in the region or on the current line. + +If there's an active region, mark macros in the region; otherwise mark +the macro on the current line. If marking the current line, move point +to the next line when done. + +Marked macros can be operated on by `kmacro-menu-do-copy' and +`kmacro-menu-do-delete'." + (declare (modes kmacro-menu-mode)) + (interactive nil kmacro-menu-mode) + (kmacro-menu--query-revert) + (kmacro-menu--do-region + (lambda (id) + (setf (alist-get (kmacro-menu--id-position id) + kmacro-menu--marks) + t) + (kmacro-menu--propertize-keys 'kmacro-menu-marked) + (tabulated-list-put-tag #("*" 0 1 (face kmacro-menu-mark)))))) + +(defun kmacro-menu-flag-for-deletion () + "Flag macros in the region or on the current line. + +If there's an active region, flag macros in the region; otherwise flag +the macro on the current line. If there is no active region, move point +to the next line when done. + +Flagged macros can be deleted via `kmacro-menu-do-flagged-delete'." + (declare (modes kmacro-menu-mode)) + (interactive nil kmacro-menu-mode) + (kmacro-menu--query-revert) + (kmacro-menu--do-region + (lambda (id) + (setf (alist-get (kmacro-menu--id-position id) + kmacro-menu--deletion-flags) + t) + (kmacro-menu--propertize-keys 'kmacro-menu-flagged) + (tabulated-list-put-tag #("D" 0 1 (face kmacro-menu-mark)))))) + +(defun kmacro-menu-unmark () + "Unmark and unflag macros in the region or on the current line. + +If there's an active region, unmark and unflag macros in the region; +otherwise unmark and unflag the macro on the current line. If there is +no active region, move point to the next line when done." + (declare (modes kmacro-menu-mode)) + (interactive nil kmacro-menu-mode) + (kmacro-menu--query-revert) + (kmacro-menu--do-region + (lambda (id) + (let ((pos (kmacro-menu--id-position id))) + (setf (alist-get pos kmacro-menu--deletion-flags) nil + (alist-get pos kmacro-menu--marks) nil)) + (kmacro-menu--propertize-keys 'default) + (tabulated-list-put-tag " ")))) + +(defun kmacro-menu-unmark-backward () + "Like `kmacro-menu-unmark', but move backwards instead of forwards." + (declare (modes kmacro-menu-mode)) + (interactive nil kmacro-menu-mode) + (kmacro-menu--query-revert) + (let ((go-back (not (use-region-p)))) + (kmacro-menu-unmark) + (when go-back + (forward-line -2)))) + +(defun kmacro-menu-unmark-all () + "Unmark and unflag all listed keyboard macros." + (declare (modes kmacro-menu-mode)) + (interactive nil kmacro-menu-mode) + (kmacro-menu--query-revert) + (setq-local kmacro-menu--deletion-flags nil + kmacro-menu--marks nil) + (save-excursion + (goto-char (point-min)) + (while (tabulated-list-get-id) + (kmacro-menu--propertize-keys 'default) + (forward-line 1)) + (tabulated-list-clear-all-tags))) + +;;;; Commands that Modify the Ring + +(defun kmacro-menu-do-flagged-delete () + "Delete keyboard macros flagged via `kmacro-menu-flag-for-deletion'." + (declare (modes kmacro-menu-mode)) + (interactive nil kmacro-menu-mode) + (kmacro-menu--query-revert) + (let ((res) + (num-deletes 0)) + (kmacro-menu--map-ids (lambda (id) + (if (alist-get (kmacro-menu--id-position id) + kmacro-menu--deletion-flags) + (setq num-deletes (1+ num-deletes)) + (push (kmacro-menu--id-kmacro id) res)))) + (when (yes-or-no-p (if (= 1 num-deletes) + "Delete 1 flagged keyboard macro?" + (format "Delete %d flagged keyboard macros?" + num-deletes))) + (kmacro-menu--replace-all + (nreverse res)) + (tabulated-list-revert)))) + +(defun kmacro-menu-do-copy () + "Duplicate macros in the region, those with markers, or the one at point. + +Macros are duplicated at their current position in the macro ring. + +If there's an active region, duplicate macros in the region; otherwise +duplicate the marked macros or, if there are no marks, the macro on the +current line." + (declare (modes kmacro-menu-mode)) + (interactive nil kmacro-menu-mode) + (kmacro-menu--query-revert) + (let* ((region-exists (use-region-p)) + (mark-exists (kmacro-menu--marks-exist-p)) + (id-alist (if (or region-exists + (not mark-exists)) + (let ((region-alist)) + (kmacro-menu--do-region + (lambda (id) + (push (cons (kmacro-menu--id-position id) + t) + region-alist))) + region-alist) + kmacro-menu--marks)) + (num-duplicates 0)) + (let ((res)) + (kmacro-menu--map-ids (lambda (id) + (let ((pos (kmacro-menu--id-position id)) + (km (kmacro-menu--id-kmacro id))) + (push km res) + (when (alist-get pos id-alist) + (push km res) + (setq num-duplicates (1+ num-duplicates)))))) + ;; Confirm the action if we operated on marks or the region, but + ;; don't confirm if operating on a single line without a region. + (when (if (or mark-exists region-exists) + (yes-or-no-p (if (= 1 num-duplicates) + "Copy (duplicate) 1 keyboard macro?" + (format "Copy (duplicate) %d keyboard macros?" + num-duplicates))) + t) + (kmacro-menu--replace-all (nreverse res)) + (tabulated-list-revert))))) + +(defun kmacro-menu-do-delete () + "Delete macros in the region, those with markers, or the one at point. + +If there's an active region, delete macros in the region; otherwise +delete the marked macros or, if there are no marks, the macro on the +current line." + (declare (modes kmacro-menu-mode)) + (interactive nil kmacro-menu-mode) + (kmacro-menu--query-revert) + (let ((num-deletes 0) + (id-alist (if (or (use-region-p) + (not (kmacro-menu--marks-exist-p))) + (let ((region-alist)) + (kmacro-menu--do-region + (lambda (id) + (push (cons (kmacro-menu--id-position id) + t) + region-alist))) + region-alist) + kmacro-menu--marks))) + (let ((res)) + (kmacro-menu--map-ids (lambda (id) + (if (alist-get (kmacro-menu--id-position id) + id-alist) + (setq num-deletes (1+ num-deletes)) + (push (kmacro-menu--id-kmacro id) res)))) + (when (yes-or-no-p (if (= 1 num-deletes) + "Delete 1 keyboard macro?" + (format "Delete %d keyboard macros?" + num-deletes))) + (kmacro-menu--replace-all (nreverse res)) + (tabulated-list-revert))))) + +;;;; Commands that Modify a Keyboard Macro + +(defun kmacro-menu-edit-position () + "Move the keyboard macro at point to a new position. + +See the Info node `(emacs) Keyboard Macro Ring' for more information." + (declare (modes kmacro-menu-mode)) + (interactive nil kmacro-menu-mode) + (let ((id (tabulated-list-get-id))) + (kmacro-menu--assert-row id) + (kmacro-menu--query-revert) + (let* ((new-position (min (length tabulated-list-entries) + (max 0 + (read-number "New position: " 0)))) + (old-km (kmacro-menu--id-kmacro id)) + (old-pos (kmacro-menu--id-position id))) + (unless (= old-pos new-position) + (kmacro-menu--replace-all + (let ((res) + (true-new-pos (if (> new-position old-pos) + (1+ new-position) + new-position))) + (kmacro-menu--map-ids (lambda (this-id) + (let ((this-km (kmacro-menu--id-kmacro this-id)) + (this-pos (kmacro-menu--id-position this-id))) + (unless (= old-pos this-pos) + (when (= this-pos true-new-pos) + (push old-km res)) + (push this-km res))))) + (when (>= true-new-pos + (length tabulated-list-entries)) + (push old-km res)) + (nreverse res))) + (tabulated-list-revert))))) + +(defun kmacro-menu-transpose () + "Swap the keyboard macro at point with the one above, then move to the next line. + +If point is on the first line (position number 0), then swap the macros +at position numbers 0 and 1, then move point to the third line. + +Note that this is the earlier position in the ring, not the sorted +table." + (declare (modes kmacro-menu-mode)) + (interactive nil kmacro-menu-mode) + (let ((id (tabulated-list-get-id))) + (kmacro-menu--assert-row id) + (kmacro-menu--query-revert) + (let* ((old-pos (kmacro-menu--id-position id)) + (first-line (= 0 old-pos)) + (end-lines-forward (if first-line + 2 + (+ 3 old-pos)))) + ;; When transposing the first two macros, we don't use + ;; `kmacro-swap-ring' here because it is possible for the user to + ;; choose to not refresh the table when it is out of date. + (kmacro-menu--replace-all + (let ((res)) + (kmacro-menu--map-ids + (if first-line + (let ((old-km (kmacro-menu--id-kmacro id))) + (lambda (this-id) + (let ((this-pos (kmacro-menu--id-position this-id))) + (unless (= 0 this-pos) + (push (kmacro-menu--id-kmacro this-id) res) + (when (= 1 this-pos) + (push old-km res)))))) + (let ((new-pos (1- old-pos))) + (lambda (this-id) + (let ((this-pos (kmacro-menu--id-position this-id))) + (unless (= old-pos this-pos) + (when (= new-pos this-pos) + (push (kmacro-menu--id-kmacro id) res)) + (push (kmacro-menu--id-kmacro this-id) res))))))) + (nreverse res))) + (tabulated-list-revert) + (goto-char (point-min)) + (forward-line end-lines-forward)))) + +(defun kmacro-menu-edit-format () + "Edit the counter format of the keyboard macro at point. + +Valid counter formats are those for integers accepted by the function +`format'. + +See the command `kmacro-set-format' and the Info node `(emacs) Keyboard +Macro Counter' for more information." + (declare (modes kmacro-menu-mode)) + (interactive nil kmacro-menu-mode) + (let ((id (tabulated-list-get-id))) + (kmacro-menu--assert-row id) + (kmacro-menu--query-revert) + (let ((km (kmacro-menu--id-kmacro id))) + (kmacro-menu--replace-at + (kmacro (kmacro--keys km) + (kmacro--counter km) + (read-string "New format: " nil nil + (list kmacro-default-counter-format + (kmacro--format km)))) + (kmacro-menu--id-position id)) + (tabulated-list-revert)))) + +(defun kmacro-menu-edit-counter () + "Edit the counter of the keyboard macro at point. + +See Info node `(emacs) Keyboard Macro Counter' for more +information." + (declare (modes kmacro-menu-mode)) + (interactive nil kmacro-menu-mode) + (let ((id (tabulated-list-get-id))) + (kmacro-menu--assert-row id) + (kmacro-menu--query-revert) + (let ((km (kmacro-menu--id-kmacro id))) + (kmacro-menu--replace-at + (kmacro (kmacro--keys km) + (read-number "New counter: " + (list 0 + (kmacro--counter + (kmacro-menu--id-kmacro id)))) + (kmacro--format km)) + (kmacro-menu--id-position id)) + (tabulated-list-revert)))) + +(defun kmacro-menu-edit-keys () + "Edit the keys of the keyboard macro at point via `edmacro-mode'. + +See Info node `(emacs) Edit Keyboard Macro' for more +information." + (declare (modes kmacro-menu-mode)) + (interactive nil kmacro-menu-mode) + (let ((id (tabulated-list-get-id))) + (kmacro-menu--assert-row id) + (kmacro-menu--query-revert) + (let* ((old-km (kmacro-menu--id-kmacro id))) + (edit-kbd-macro (kmacro--keys old-km) + nil + nil + (lambda (mac) + (kmacro-menu--replace-at + (kmacro mac + (kmacro--counter old-km) + (kmacro--format old-km)) + (kmacro-menu--id-position id)) + (tabulated-list-revert)))))) + +(defun kmacro-menu-edit-column () + "Edit the value in the current column of the keyboard macro at point." + (declare (modes kmacro-menu-mode)) + (interactive nil kmacro-menu-mode) + (kmacro-menu--assert-row) + (kmacro-menu--query-revert) + (pcase (get-text-property (point) 'tabulated-list-column-name) + ('nil (let ((pos (point))) + ;; If we didn't find a column, try moving forwards or + ;; backwards to the nearest column. + (tabulated-list-next-column 1) + (when (= pos (point)) + (tabulated-list-previous-column 1)) + (if (null (get-text-property (point) 'tabulated-list-column-name)) + (user-error "No column at point") + (kmacro-menu-edit-column)))) + ("Position" (call-interactively #'kmacro-menu-edit-position)) + ("Counter" (call-interactively #'kmacro-menu-edit-counter)) + ("Format" (call-interactively #'kmacro-menu-edit-format)) + ("Formatted" (user-error "Formatted counter is not editable")) + ("Keys" (call-interactively #'kmacro-menu-edit-keys)))) + (provide 'kmacro) ;;; kmacro.el ends here commit b2842b25bf7fc934cf86b82d1053db55fd55c00b Author: Alan Mackenzie Date: Sun Apr 14 08:21:56 2024 +0000 CC Mode: Don't start fontifying in the middle of an identifier This fixes bug#70367. * lisp/progmodes/cc-mode.el (c-fl-decl-start): After searching backwards for the end of the previous statement, check whether or not we found it. diff --git a/lisp/progmodes/cc-mode.el b/lisp/progmodes/cc-mode.el index 1a9d0907bd0..5f11622733f 100644 --- a/lisp/progmodes/cc-mode.el +++ b/lisp/progmodes/cc-mode.el @@ -2463,7 +2463,7 @@ with // and /*, not more generic line and block comments." (backward-char) (setq pseudo (c-cheap-inside-bracelist-p (c-parse-state))))))) (goto-char pseudo)) - t) + (or pseudo (> (point) bod-lim))) ;; Move forward to the start of the next declaration. (progn (c-forward-syntactic-ws) ;; Have we got stuck in a comment at EOB?