commit b305f5a3ab96f828bb8b41a702758314b924139c (HEAD, refs/remotes/origin/master) Author: Po Lu Date: Fri Mar 4 14:16:15 2022 +0800 Correctly process legacy XI wheel events on top of scroll bars * src/xterm.c (handle_one_xevent): Handle XI button events generated by scroll wheels on top of scroll bars. diff --git a/src/xterm.c b/src/xterm.c index b9a4328c29..2c6289a9cb 100644 --- a/src/xterm.c +++ b/src/xterm.c @@ -12236,6 +12236,48 @@ handle_one_xevent (struct x_display_info *dpyinfo, } #ifdef USE_GTK + if (!f) + { + int real_x = lrint (xev->event_x); + int real_y = lrint (xev->event_y); + Window child; + + f = x_any_window_to_frame (dpyinfo, xev->event); + + if (xev->detail > 3 && xev->detail < 9 && f) + { + if (xev->evtype == XI_ButtonRelease) + { + if (FRAME_X_WINDOW (f) != xev->event) + XTranslateCoordinates (dpyinfo->display, xev->event, + FRAME_X_WINDOW (f), real_x, + real_y, &real_x, &real_y, &child); + + if (xev->detail <= 5) + inev.ie.kind = WHEEL_EVENT; + else + inev.ie.kind = HORIZ_WHEEL_EVENT; + + inev.ie.timestamp = xev->time; + + XSETINT (inev.ie.x, real_x); + XSETINT (inev.ie.y, real_y); + XSETFRAME (inev.ie.frame_or_window, f); + + inev.ie.modifiers + |= x_x_to_emacs_modifiers (dpyinfo, + xev->mods.effective); + + inev.ie.modifiers |= xev->detail % 2 ? down_modifier : up_modifier; + } + + *finish = X_EVENT_DROP; + goto XI_OTHER; + } + else + f = NULL; + } + if (f && xg_event_is_for_scrollbar (f, event, false)) f = 0; #endif commit 6058daedf72d1170738e69a3b23f2c4c2a163ca7 Author: Po Lu Date: Fri Mar 4 09:42:33 2022 +0800 Pass core scroll wheel events outside the edit widget to Emacs on GTK * src/xterm.c (x_construct_mouse_click): Translate coordinates if the event window is not the edit widget window. (handle_one_xevent): Treat core scroll wheel events specially, if mouse_or_wdesc_frame did not find the frame. diff --git a/src/xterm.c b/src/xterm.c index 2563fb31a5..b9a4328c29 100644 --- a/src/xterm.c +++ b/src/xterm.c @@ -6813,6 +6813,10 @@ x_construct_mouse_click (struct input_event *result, const XButtonEvent *event, struct frame *f) { + int x = event->x; + int y = event->y; + Window dummy; + /* Make the event type NO_EVENT; we'll change that when we decide otherwise. */ result->kind = MOUSE_CLICK_EVENT; @@ -6824,8 +6828,16 @@ x_construct_mouse_click (struct input_event *result, ? up_modifier : down_modifier)); - XSETINT (result->x, event->x); - XSETINT (result->y, event->y); + /* If result->window is not the frame's edit widget (which can + happen with GTK+ scroll bars, for example), translate the + coordinates so they appear at the correct position. */ + if (event->window != FRAME_X_WINDOW (f)) + XTranslateCoordinates (FRAME_X_DISPLAY (f), + event->window, FRAME_X_WINDOW (f), + x, y, &x, &y, &dummy); + + XSETINT (result->x, x); + XSETINT (result->y, y); XSETFRAME (result->frame_or_window, f); result->arg = Qnil; return Qnil; @@ -11275,6 +11287,35 @@ handle_one_xevent (struct x_display_info *dpyinfo, } #ifdef USE_GTK + if (!f) + { + f = x_any_window_to_frame (dpyinfo, event->xbutton.window); + + if (event->xbutton.button > 3 + && event->xbutton.button < 9 + && f) + { + if (ignore_next_mouse_click_timeout) + { + if (event->type == ButtonPress + && event->xbutton.time > ignore_next_mouse_click_timeout) + { + ignore_next_mouse_click_timeout = 0; + x_construct_mouse_click (&inev.ie, &event->xbutton, f); + } + if (event->type == ButtonRelease) + ignore_next_mouse_click_timeout = 0; + } + else + x_construct_mouse_click (&inev.ie, &event->xbutton, f); + + *finish = X_EVENT_DROP; + goto OTHER; + } + else + f = NULL; + } + if (f && xg_event_is_for_scrollbar (f, event, false)) f = 0; #endif commit 4df7bb9c0112007d321815d00c9dca8b44b4c9b7 Author: Po Lu Date: Fri Mar 4 09:13:08 2022 +0800 Fix display of stretch glyphs when hscrolled on PGTK * pgtkterm.c (x_draw_stretch_glyph_string): Use correct box dimensions. diff --git a/src/pgtkterm.c b/src/pgtkterm.c index ce167fdac2..41e2f0ded0 100644 --- a/src/pgtkterm.c +++ b/src/pgtkterm.c @@ -2379,24 +2379,21 @@ x_draw_stretch_glyph_string (struct glyph_string *s) header line and mode line. */ if (x < text_left_x && !s->row->mode_line_p) { - int left_x = WINDOW_LEFT_SCROLL_BAR_AREA_WIDTH (s->w); - int right_x = text_left_x; + int background_width = s->background_width; + int x = s->x, text_left_x = window_box_left (s->w, TEXT_AREA); - if (WINDOW_HAS_FRINGES_OUTSIDE_MARGINS (s->w)) - left_x += WINDOW_LEFT_FRINGE_WIDTH (s->w); - else - right_x -= WINDOW_LEFT_FRINGE_WIDTH (s->w); - - /* Adjust X and BACKGROUND_WIDTH to fit inside the space - between LEFT_X and RIGHT_X. */ - if (x < left_x) + /* Don't draw into left fringe or scrollbar area except for + header line and mode line. */ + if (s->area == TEXT_AREA + && x < text_left_x && !s->row->mode_line_p) { - background_width -= left_x - x; - x = left_x; + background_width -= text_left_x - x; + x = text_left_x; } - if (x + background_width > right_x) - background_width = right_x - x; + if (background_width > 0) + x_draw_glyph_string_bg_rect (s, x, s->y, background_width, s->height); } + if (background_width > 0) x_draw_glyph_string_bg_rect (s, x, s->y, background_width, s->height); } commit cba88c275bb2abbd65837fb27530655b0222b62b Author: Po Lu Date: Fri Mar 4 09:11:36 2022 +0800 * src/pgtkterm.c (pgtk_draw_fringe_bitmap): Synchronize logic with X. diff --git a/src/pgtkterm.c b/src/pgtkterm.c index 7855b7053a..ce167fdac2 100644 --- a/src/pgtkterm.c +++ b/src/pgtkterm.c @@ -3573,10 +3573,23 @@ pgtk_draw_fringe_bitmap (struct window *w, struct glyph_row *row, } } - if (p->which && p->which < max_fringe_bmp) + if (p->which + && p->which < max_fringe_bmp + && p->which < max_used_fringe_bitmap) { Emacs_GC gcv; + if (!fringe_bmp[p->which]) + { + /* This fringe bitmap is known to fringe.c, but lacks the + cairo_pattern_t pattern which shadows that bitmap. This + is typical to define-fringe-bitmap being called when the + selected frame was not a GUI frame, for example, when + packages that define fringe bitmaps are loaded by a + daemon Emacs. Create the missing pattern now. */ + gui_define_fringe_bitmap (f, p->which); + } + gcv.foreground = (p->cursor_p ? (p->overlay_p ? face->background : FRAME_X_OUTPUT (f)->cursor_color) commit 848e96acdd7c77a9773366fa9e297fb49eb9b8c0 Author: Po Lu Date: Fri Mar 4 09:08:33 2022 +0800 ; * src/nsterm.m (ns_draw_fringe_bitmap): Fix typo in last change. diff --git a/src/nsterm.m b/src/nsterm.m index 639f8781e4..45561b88b5 100644 --- a/src/nsterm.m +++ b/src/nsterm.m @@ -2917,7 +2917,8 @@ Hide the window (X11 semantics) NSBezierPath *bmp = [fringe_bmp objectForKey:[NSNumber numberWithInt:p->which]]; - if (bmp == nil) + if (bmp == nil + && p->which < max_used_fringe_bitmap) { gui_define_fringe_bitmap (f, p->which); bmp = [fringe_bmp objectForKey: [NSNumber numberWithInt: p->which]]; commit a421f40d8765262b49d89804e96757671456d97d Author: Po Lu Date: Fri Mar 4 09:06:20 2022 +0800 Synchronize NS fringe bitmap code with X * nsterm.m (ns_draw_fringe_bitmap): Redefine bitmap if it does not already exist. diff --git a/src/nsterm.m b/src/nsterm.m index aba26ef758..639f8781e4 100644 --- a/src/nsterm.m +++ b/src/nsterm.m @@ -2916,6 +2916,13 @@ Hide the window (X11 semantics) } NSBezierPath *bmp = [fringe_bmp objectForKey:[NSNumber numberWithInt:p->which]]; + + if (bmp == nil) + { + gui_define_fringe_bitmap (f, p->which); + bmp = [fringe_bmp objectForKey: [NSNumber numberWithInt: p->which]]; + } + if (bmp) { NSAffineTransform *transform = [NSAffineTransform transform]; commit 77483ac8819f982289fa0b448802291b4d83ae95 Author: Po Lu Date: Fri Mar 4 01:01:07 2022 +0000 Synchronize Haiku fringe bitmap code with X * haikuterm.c (haiku_draw_fringe_bitmap): (haiku_define_fringe_bitmap): (haiku_destroy_fringe_bitmap): Synchronize logic with X. diff --git a/src/haikuterm.c b/src/haikuterm.c index bdba6403ac..833b3bee2a 100644 --- a/src/haikuterm.c +++ b/src/haikuterm.c @@ -44,7 +44,7 @@ struct haiku_display_info *x_display_list = NULL; extern frame_parm_handler haiku_frame_parm_handlers[]; static void **fringe_bmps; -static int fringe_bitmap_fillptr = 0; +static int max_fringe_bmp = 0; static Lisp_Object rdb; @@ -2326,10 +2326,24 @@ haiku_draw_fringe_bitmap (struct window *w, struct glyph_row *row, BView_FillRectangle (view, p->bx, p->by, p->nx, p->ny); } - if (p->which && p->which < fringe_bitmap_fillptr) + if (p->which + && p->which < max_fringe_bmp + && p->which < max_used_fringe_bitmap) { void *bitmap = fringe_bmps[p->which]; + if (!bitmap) + { + /* This fringe bitmap is known to fringe.c, but lacks the + BBitmap which shadows that bitmap. This is typical to + define-fringe-bitmap being called when the selected frame + was not a GUI frame, for example, when packages that + define fringe bitmaps are loaded by a daemon Emacs. + Create the missing pattern now. */ + gui_define_fringe_bitmap (WINDOW_XFRAME (w), p->which); + bitmap = fringe_bmps[p->which]; + } + uint32_t col; if (!p->cursor_p) @@ -2357,14 +2371,14 @@ static void haiku_define_fringe_bitmap (int which, unsigned short *bits, int h, int wd) { - if (which >= fringe_bitmap_fillptr) + if (which >= max_fringe_bmp) { - int i = fringe_bitmap_fillptr; - fringe_bitmap_fillptr = which + 20; - fringe_bmps = !i ? xmalloc (fringe_bitmap_fillptr * sizeof (void *)) : - xrealloc (fringe_bmps, fringe_bitmap_fillptr * sizeof (void *)); + int i = max_fringe_bmp; + max_fringe_bmp = which + 20; + fringe_bmps = !i ? xmalloc (max_fringe_bmp * sizeof (void *)) : + xrealloc (fringe_bmps, max_fringe_bmp * sizeof (void *)); - while (i < fringe_bitmap_fillptr) + while (i < max_fringe_bmp) fringe_bmps[i++] = NULL; } @@ -2379,7 +2393,7 @@ haiku_define_fringe_bitmap (int which, unsigned short *bits, static void haiku_destroy_fringe_bitmap (int which) { - if (which >= fringe_bitmap_fillptr) + if (which >= max_fringe_bmp) return; if (fringe_bmps[which]) commit 18868de46340ce8a1b2c2a1d9d81364530509e98 Merge: aeb25f9d3d 29ff903bb0 Author: Po Lu Date: Fri Mar 4 00:52:18 2022 +0000 Merge from origin/emacs-28 29ff903bb0 Avoid crashes when fringe bitmaps are defined in daemon mode 92e2d19fe7 One more fix of the BPA implementation cd51d9c7ab Fix handling of brackets in BPA commit aeb25f9d3d12a18ef3881e23b32a34615355d4d0 Author: Philipp Stephani Date: Thu Mar 3 19:56:09 2022 +0100 Teach Edebug about the special '&whole' syntax for compiler macros. * lisp/emacs-lisp/cl-macs.el (cl-define-compiler-macro-list): New Edebug element specification. (cl-define-compiler-macro): Use it. * test/lisp/emacs-lisp/cl-macs-tests.el (cl-define-compiler-macro/edebug): New unit test. diff --git a/lisp/emacs-lisp/cl-macs.el b/lisp/emacs-lisp/cl-macs.el index 5085217250..accd70dc4e 100644 --- a/lisp/emacs-lisp/cl-macs.el +++ b/lisp/emacs-lisp/cl-macs.el @@ -3489,6 +3489,10 @@ omitted, a default message listing FORM itself is used." ;;; Compiler macros. +(def-edebug-elem-spec 'cl-define-compiler-macro-list + `(([&optional "&whole" arg] + ,@(car (get 'cl-macro-list 'edebug-elem-spec))))) + ;;;###autoload (defmacro cl-define-compiler-macro (func args &rest body) "Define a compiler-only macro. @@ -3501,7 +3505,10 @@ compiler macros are expanded repeatedly until no further expansions are possible. Unlike regular macros, BODY can decide to \"punt\" and leave the original function call alone by declaring an initial `&whole foo' parameter and then returning foo." - (declare (debug cl-defmacro) (indent 2)) + ;; Like `cl-defmacro', but with the `&whole' special case. + (declare (debug (&define name cl-define-compiler-macro-list + cl-declarations-or-string def-body)) + (indent 2)) (let ((p args) (res nil)) (while (consp p) (push (pop p) res)) (setq args (nconc (nreverse res) (and p (list '&rest p))))) diff --git a/test/lisp/emacs-lisp/cl-macs-tests.el b/test/lisp/emacs-lisp/cl-macs-tests.el index 008ec0de4a..036ee30966 100644 --- a/test/lisp/emacs-lisp/cl-macs-tests.el +++ b/test/lisp/emacs-lisp/cl-macs-tests.el @@ -23,6 +23,7 @@ (require 'cl-lib) (require 'cl-macs) +(require 'edebug) (require 'ert) @@ -694,4 +695,18 @@ collection clause." (list cl-macs--test1 cl-macs--test2)) '(1 2)))) +(ert-deftest cl-define-compiler-macro/edebug () + "Check that we can instrument compiler macros." + (with-temp-buffer + (dolist (form '((defun cl-define-compiler-macro/edebug (a b) nil) + (cl-define-compiler-macro + cl-define-compiler-macro/edebug + (&whole w a b) + w))) + (print form (current-buffer))) + (let ((edebug-all-defs t) + (edebug-initial-mode 'Go-nonstop)) + ;; Just make sure the forms can be instrumented. + (eval-buffer)))) + ;;; cl-macs-tests.el ends here commit 29ff903bb0379f6fef0f7dc60977e05a8c60f147 (refs/remotes/origin/emacs-28) Author: Eli Zaretskii Date: Thu Mar 3 20:31:33 2022 +0200 Avoid crashes when fringe bitmaps are defined in daemon mode * src/dispextern.h (gui_define_fringe_bitmap): Add prototype. (max_used_fringe_bitmap): Add declaration. * src/fringe.c (gui_define_fringe_bitmap): New function. * src/w32term.c (w32_draw_fringe_bitmap): * src/xterm.c (x_draw_fringe_bitmap) [USE_CAIRO]: Call 'gui_define_fringe_bitmap' if the terminal-specific bitmap data is not available when a fringe bitmap is about to be drawn. Don't try to draw a bitmap that is not known to fringe.c. (Bug#54183) diff --git a/src/dispextern.h b/src/dispextern.h index bc5f7a52e0..65801596d5 100644 --- a/src/dispextern.h +++ b/src/dispextern.h @@ -3449,6 +3449,9 @@ bool update_window_fringes (struct window *, bool); void gui_init_fringe (struct redisplay_interface *); +extern int max_used_fringe_bitmap; +void gui_define_fringe_bitmap (struct frame *, int); + #ifdef HAVE_NTGUI void w32_reset_fringes (void); #endif diff --git a/src/fringe.c b/src/fringe.c index f857aedaf0..14148a67ab 100644 --- a/src/fringe.c +++ b/src/fringe.c @@ -1802,6 +1802,23 @@ gui_init_fringe (struct redisplay_interface *rif) } } +/* Call frame F's specific define_fringe_bitmap method for a fringe + bitmap number N. Called by various *term.c functions when they + need to display a fringe bitmap whose terminal-specific data is not + available. */ +void +gui_define_fringe_bitmap (struct frame *f, int n) +{ + struct redisplay_interface *rif = FRAME_RIF (f); + + if (!rif || !rif->define_fringe_bitmap || n >= max_used_fringe_bitmap) + return; + + struct fringe_bitmap *fb = fringe_bitmaps[n]; + if (fb) + rif->define_fringe_bitmap (n, fb->bits, fb->height, fb->width); +} + #ifdef HAVE_NTGUI void w32_reset_fringes (void) diff --git a/src/w32term.c b/src/w32term.c index 6b41b1d324..ae99d9948e 100644 --- a/src/w32term.c +++ b/src/w32term.c @@ -777,12 +777,25 @@ w32_draw_fringe_bitmap (struct window *w, struct glyph_row *row, w32_fill_area (f, hdc, face->background, p->bx, p->by, p->nx, p->ny); - if (p->which && p->which < max_fringe_bmp) + if (p->which + && p->which < max_fringe_bmp + && p->which < max_used_fringe_bitmap) { HBITMAP pixmap = fringe_bmp[p->which]; HDC compat_hdc; HANDLE horig_obj; + if (!fringe_bmp[p->which]) + { + /* This fringe bitmap is known to fringe.c, but lacks the + HBITMAP data which shadows that bitmap. This is typical + to define-fringe-bitmap being called when the selected + frame was not a GUI frame, for example, when packages + that define fringe bitmaps are loaded by a daemon Emacs. + Create the missing HBITMAP now. */ + gui_define_fringe_bitmap (f, p->which); + } + compat_hdc = CreateCompatibleDC (hdc); SaveDC (hdc); diff --git a/src/xterm.c b/src/xterm.c index 59413eafd4..9a8c3e9ad7 100644 --- a/src/xterm.c +++ b/src/xterm.c @@ -1426,7 +1426,9 @@ x_draw_fringe_bitmap (struct window *w, struct glyph_row *row, struct draw_fring } #ifdef USE_CAIRO - if (p->which && p->which < max_fringe_bmp) + if (p->which + && p->which < max_fringe_bmp + && p->which < max_used_fringe_bitmap) { XGCValues gcv; @@ -1436,6 +1438,16 @@ x_draw_fringe_bitmap (struct window *w, struct glyph_row *row, struct draw_fring : f->output_data.x->cursor_pixel) : face->foreground)); XSetBackground (display, gc, face->background); + if (!fringe_bmp[p->which]) + { + /* This fringe bitmap is known to fringe.c, but lacks the + cairo_pattern_t pattern which shadows that bitmap. This + is typical to define-fringe-bitmap being called when the + selected frame was not a GUI frame, for example, when + packages that define fringe bitmaps are loaded by a + daemon Emacs. Create the missing pattern now. */ + gui_define_fringe_bitmap (f, p->which); + } x_cr_draw_image (f, gc, fringe_bmp[p->which], 0, p->dh, p->wd, p->h, p->x, p->y, p->overlay_p); XSetForeground (display, gc, gcv.foreground); commit dc8a692f97fdafaa31001ab0620394525103ddb7 Author: Stefan Monnier Date: Thu Mar 3 10:12:44 2022 -0500 (with-demoted-errors): Fix bug#54225 * lisp/subr.el (with-demoted-errors): Adjust to argument order of `macroexp-warn-and-return` changed in f262a6af3694b41828ff. diff --git a/lisp/subr.el b/lisp/subr.el index eb9af0b36d..2321765f95 100644 --- a/lisp/subr.el +++ b/lisp/subr.el @@ -4560,7 +4560,7 @@ but that should be robust in the unexpected case that an error is signaled." (if (eq orig-body body) exp ;; The use without `format' is obsolete, let's warn when we bump ;; into any such remaining uses. - (macroexp-warn-and-return format "Missing format argument" exp)))) + (macroexp-warn-and-return "Missing format argument" exp nil nil format)))) (defmacro combine-after-change-calls (&rest body) "Execute BODY, but don't call the after-change functions till the end. commit b6587090bfa192efc4db5ac181a9dfd5ee8c0c08 Author: Lars Ingebrigtsen Date: Thu Mar 3 15:43:35 2022 +0100 Fix clobbering of match data in executable-set-magic * lisp/progmodes/executable.el (executable-set-magic): Switching buffers may clobber match data, so save the match data first (bug#54218). diff --git a/lisp/progmodes/executable.el b/lisp/progmodes/executable.el index d7c093444e..670b6e7e89 100644 --- a/lisp/progmodes/executable.el +++ b/lisp/progmodes/executable.el @@ -240,12 +240,13 @@ executable." (not (string= argument (buffer-substring (point) (match-end 1)))) (if (or (not executable-query) no-query-flag - (save-window-excursion - ;; Make buffer visible before question. - (switch-to-buffer (current-buffer)) - (y-or-n-p (format-message - "Replace magic number by `#!%s'? " - argument)))) + (save-match-data + (save-window-excursion + ;; Make buffer visible before question. + (switch-to-buffer (current-buffer)) + (y-or-n-p (format-message + "Replace magic number by `#!%s'? " + argument))))) (progn (replace-match argument t t nil 1) (message "Magic number changed to `#!%s'" argument)))) commit d72cd4a2b761d325e5bb3e664781a4c9001eb2c2 Author: Jim Porter Date: Tue Mar 1 18:53:42 2022 -0800 Allow splitting strings in Eshell expansions with "plain" strings Since '$var[hello 0]' doesn't make sense when 'var' is a string, the previous restriction was unnecessary. * lisp/eshell/esh-var.el (Commentary): Update documentation. (eshell-apply-indices): Allow "plain" strings to split strings. * test/lisp/eshell/esh-var-test.el (esh-var-test/interp-var-string-split-indices) (esh-var-test/quoted-interp-var-string-split-indices): Update tests. * doc/misc/eshell.texi (Dollars expansion): Update documentation. diff --git a/doc/misc/eshell.texi b/doc/misc/eshell.texi index 3301a854eb..5581e5cd9e 100644 --- a/doc/misc/eshell.texi +++ b/doc/misc/eshell.texi @@ -1055,9 +1055,9 @@ Multiple sets of indices can also be specified. For example, if @item $@var{expr}[@var{regexp} @var{i...}] As above (when @var{expr} expands to a string), but use @var{regexp} -to split the string. @var{regexp} can be any form other than a number -or a plain variable name. For example, @samp{$@var{var}[: 0]} will -return the first element of a colon-delimited string. +to split the string. @var{regexp} can be any form other than a +number. For example, @samp{$@var{var}[: 0]} will return the first +element of a colon-delimited string. @item $#@var{expr} Expands to the length of the result of @var{expr}, an expression in diff --git a/lisp/eshell/esh-var.el b/lisp/eshell/esh-var.el index 6f08a3fbc4..af89e35f55 100644 --- a/lisp/eshell/esh-var.el +++ b/lisp/eshell/esh-var.el @@ -74,9 +74,8 @@ ;; $EXPR["\\\\" 10] ;; ;; Separate on backslash characters. Actually, the first argument -- -;; if it doesn't have the form of a number, or a plain variable name -;; -- can be any regular expression. So to split on numbers, use -;; '$EXPR["[0-9]+" 10 20]'. +;; if it doesn't have the form of a number -- can be any regular +;; expression. So to split on numbers, use '$EXPR["[0-9]+" 10 20]'. ;; ;; $EXPR[hello] ;; @@ -566,13 +565,11 @@ For example, to retrieve the second element of a user's record in (while indices (let ((refs (car indices))) (when (stringp value) - (let (separator) - (if (not (or (not (stringp (caar indices))) - (string-match - (concat "^" eshell-variable-name-regexp "$") - (caar indices)))) - (setq separator (caar indices) - refs (cdr refs))) + (let (separator (index (caar indices))) + (when (and (stringp index) + (not (get-text-property 0 'number index))) + (setq separator index + refs (cdr refs))) (setq value (mapcar #'eshell-convert (split-string value separator))))) diff --git a/test/lisp/eshell/esh-var-tests.el b/test/lisp/eshell/esh-var-tests.el index e679174939..d09dd614de 100644 --- a/test/lisp/eshell/esh-var-tests.el +++ b/test/lisp/eshell/esh-var-tests.el @@ -84,6 +84,11 @@ (should (equal (eshell-test-command-result "echo $eshell-test-value[: 0]") "zero")) (should (equal (eshell-test-command-result "echo $eshell-test-value[: 0 2]") + '("zero" "two")))) + (let ((eshell-test-value "zeroXoneXtwoXthreeXfour")) + (should (equal (eshell-test-command-result "echo $eshell-test-value[X 0]") + "zero")) + (should (equal (eshell-test-command-result "echo $eshell-test-value[X 0 2]") '("zero" "two"))))) (ert-deftest esh-var-test/interp-var-regexp-split-indices () @@ -216,6 +221,13 @@ inside double-quotes" "zero")) (should (equal (eshell-test-command-result "echo \"$eshell-test-value[: 0 2]\"") + '("zero" "two")))) + (let ((eshell-test-value "zeroXoneXtwoXthreeXfour")) + (should (equal (eshell-test-command-result + "echo \"$eshell-test-value[X 0]\"") + "zero")) + (should (equal (eshell-test-command-result + "echo \"$eshell-test-value[X 0 2]\"") '("zero" "two"))))) (ert-deftest esh-var-test/quoted-interp-var-regexp-split-indices () commit 990f36fa108092c8b93692bb80c6fbd0b6b8f391 Author: Jim Porter Date: Tue Mar 1 18:36:08 2022 -0800 Fix parsing of indices in Eshell expansions Previously, more-complex index expansions, like '$var[":" 0]' or '$var[$(expr) 0]' failed to parse correctly. * lisp/eshell/esh-var.el (Commentary): Clarify indexing and length expansions. (eshell-parse-indices): Expand docstring and support parsing inside double-quotes. (eshell-eval-indices): New function. (eshell-parse-variable): Use it. * test/lisp/eshell/esh-var-tests.el (eshell-test-value): New defvar. (esh-var-test/interp-var-indices, (esh-var-test/interp-var-split-indices) (esh-var-test/interp-var-string-split-indices) (esh-var-test/interp-var-regexp-split-indices) (esh-var-test/interp-var-assoc, esh-var-test/interp-var-length-list) (esh-var-test/interp-var-length-string) (esh-var-test/interp-var-length-alist) (esh-var-test/quoted-interp-var-indices) (esh-var-test/quoted-interp-var-split-indices) (esh-var-test/quoted-interp-var-string-split-indices) (esh-var-test/quoted-interp-var-regexp-split-indices) (esh-var-test/quoted-interp-var-assoc) (esh-var-test/quoted-interp-var-length-list) (esh-var-test/quoted-interp-var-length-string) (esh-var-test/quoted-interp-var-length-alist): New tests. * doc/misc/eshell.texi (Dollars Expansion): Expand and reword documentation for indexing and length expansions. diff --git a/doc/misc/eshell.texi b/doc/misc/eshell.texi index bbf8ca6b8b..3301a854eb 100644 --- a/doc/misc/eshell.texi +++ b/doc/misc/eshell.texi @@ -1022,11 +1022,6 @@ Expands to the value bound to @var{var}. This is useful to disambiguate the variable name when concatenating it with another value, such as @samp{$"@var{var}"-suffix}. -@item $#@var{var} -Expands to the length of the value bound to @var{var}. Raises an error -if the value is not a sequence -(@pxref{Sequences Arrays Vectors, Sequences, , elisp, The Emacs Lisp Reference Manual}). - @item $(@var{lisp}) Expands to the result of evaluating the S-expression @code{(@var{lisp})}. On its own, this is identical to just @code{(@var{lisp})}, but with the @code{$}, @@ -1041,34 +1036,35 @@ As with @samp{$@{@var{command}@}}, evaluates the Eshell command invocation @command{@var{command}}, but writes the output to a temporary file and returns the file name. -@item $@var{var}[i] -Expands to the @code{i}th element of the value bound to @var{var}. If -the value is a string, it will be split at whitespace to make it a list. -Again, raises an error if the value is not a sequence. - -@item $@var{var}[: i] -As above, but now splitting occurs at the colon character. - -@item $@var{var}[: i j] -As above, but instead of returning just a string, it now returns a list -of two strings. If the result is being interpolated into a larger -string, this list will be flattened into one big string, with each -element separated by a space. - -@item $@var{var}["\\\\" i] -Separate on backslash characters. Actually, the first argument -- if it -doesn't have the form of a number, or a plain variable name -- can be -any regular expression. So to split on numbers, use -@samp{$@var{var}["[0-9]+" 10 20]}. - -@item $@var{var}[hello] -Calls @code{assoc} on @var{var} with @code{"hello"}, expecting it to be -an alist (@pxref{Association List Type, Association Lists, , elisp, -The Emacs Lisp Reference Manual}). - -@item $#@var{var}[hello] -Returns the length of the @code{cdr} of the element of @var{var} whose -car is equal to @code{"hello"}. +@item $@var{expr}[@var{i...}] +Expands to the @var{i}th element of the result of @var{expr}, an +expression in one of the above forms listed here. If multiple indices +are supplied, this will return a list containing the elements for each +index. If @var{expr}'s value is a string, it will first be split at +whitespace to make it a list. If @var{expr}'s value is an alist +(@pxref{Association List Type, Association Lists, , elisp, The Emacs +Lisp Reference Manual}), this will call @code{assoc} on the result of +@var{expr}, returning the @code{cdr} of the element of the result +whose car is equal to @code{"i"}. Raises an error if the value is not +a sequence (@pxref{Sequences Arrays Vectors, Sequences, , elisp, The +Emacs Lisp Reference Manual}). + +Multiple sets of indices can also be specified. For example, if +@var{var} is a list of lists, @samp{$@var{var}[0][0]} is equivalent to +@samp{(caar @var{var})}. + +@item $@var{expr}[@var{regexp} @var{i...}] +As above (when @var{expr} expands to a string), but use @var{regexp} +to split the string. @var{regexp} can be any form other than a number +or a plain variable name. For example, @samp{$@var{var}[: 0]} will +return the first element of a colon-delimited string. + +@item $#@var{expr} +Expands to the length of the result of @var{expr}, an expression in +one of the above forms. For example, @samp{$#@var{var}} returns the +length of the variable @var{var} and @samp{$#@var{var}[0]} returns the +length of the first element of @var{var}. Again, raises an error if +the result of @var{expr} is not a sequence. @end table diff --git a/lisp/eshell/esh-var.el b/lisp/eshell/esh-var.el index 24fdbde3cf..6f08a3fbc4 100644 --- a/lisp/eshell/esh-var.el +++ b/lisp/eshell/esh-var.el @@ -39,11 +39,6 @@ ;; ;; Only "MYVAR" is part of the variable name in this case. ;; -;; $#VARIABLE -;; -;; Returns the length of the value of VARIABLE. This could also be -;; done using the `length' Lisp function. -;; ;; $(lisp) ;; ;; Returns result of Lisp evaluation. Note: Used alone like this, it @@ -61,38 +56,36 @@ ;; Evaluates an eshell subcommand, redirecting the output to a ;; temporary file, and returning the file name. ;; -;; $ANYVAR[10] +;; $EXPR[10] ;; -;; Return the 10th element of ANYVAR. If ANYVAR's value is a string, -;; it will be split in order to make it a list. The splitting will -;; occur at whitespace. +;; Return the 10th element of $EXPR, which can be any dollar +;; expression. If $EXPR's value is a string, it will be split in +;; order to make it a list. The splitting will occur at whitespace. ;; -;; $ANYVAR[: 10] +;; $EXPR[10 20] ;; -;; As above, except that splitting occurs at the colon now. +;; As above, but instead of returning a single element, it now returns a +;; list of two elements. ;; -;; $ANYVAR[: 10 20] +;; $EXPR[: 10] ;; -;; As above, but instead of returning just a string, it now returns a -;; list of two strings. If the result is being interpolated into a -;; larger string, this list will be flattened into one big string, -;; with each element separated by a space. +;; Like $EXPR[10], except that splitting occurs at the colon now. ;; -;; $ANYVAR["\\\\" 10] +;; $EXPR["\\\\" 10] ;; ;; Separate on backslash characters. Actually, the first argument -- ;; if it doesn't have the form of a number, or a plain variable name ;; -- can be any regular expression. So to split on numbers, use -;; '$ANYVAR["[0-9]+" 10 20]'. +;; '$EXPR["[0-9]+" 10 20]'. ;; -;; $ANYVAR[hello] +;; $EXPR[hello] ;; -;; Calls `assoc' on ANYVAR with 'hello', expecting it to be an alist. +;; Calls `assoc' on $EXPR with 'hello', expecting it to be an alist. ;; -;; $#ANYVAR[hello] +;; $#EXPR ;; -;; Returns the length of the cdr of the element of ANYVAR who car is -;; equal to "hello". +;; Returns the length of the value of $EXPR. This could also be +;; done using the `length' Lisp function. ;; ;; There are also a few special variables defined by Eshell. '$$' is ;; the value of the last command (t or nil, in the case of an external @@ -416,7 +409,7 @@ process any indices that come after the variable reference." (eshell-parse-indices)) ;; This is an expression that will be evaluated by `eshell-do-eval', ;; which only support let-binding of dynamically-scoped vars - value `(let ((indices ',indices)) ,value)) + value `(let ((indices (eshell-eval-indices ',indices))) ,value)) (if get-len `(length ,value) value))) @@ -504,19 +497,29 @@ Possible options are: (defvar eshell-glob-function) (defun eshell-parse-indices () - "Parse and return a list of list of indices." + "Parse and return a list of index-lists. + +For example, \"[0 1][2]\" becomes: + ((\"0\" \"1\") (\"2\")." (let (indices) (while (eq (char-after) ?\[) (let ((end (eshell-find-delimiter ?\[ ?\]))) (if (not end) (throw 'eshell-incomplete ?\[) (forward-char) - (let (eshell-glob-function) - (setq indices (cons (eshell-parse-arguments (point) end) - indices))) + (eshell-with-temp-command (or (eshell-parse-inner-double-quote end) + (cons (point) end)) + (let (eshell-glob-function (eshell-current-quoted nil)) + (setq indices (cons (eshell-parse-arguments + (point-min) (point-max)) + indices)))) (goto-char (1+ end))))) (nreverse indices))) +(defun eshell-eval-indices (indices) + "Evaluate INDICES, a list of index-lists generated by `eshell-parse-indices'." + (mapcar (lambda (i) (mapcar #'eval i)) indices)) + (defun eshell-get-variable (name &optional indices) "Get the value for the variable NAME." (let* ((alias (assoc name eshell-variable-aliases-list)) diff --git a/test/lisp/eshell/esh-var-tests.el b/test/lisp/eshell/esh-var-tests.el index 7ec6a97519..e679174939 100644 --- a/test/lisp/eshell/esh-var-tests.el +++ b/test/lisp/eshell/esh-var-tests.el @@ -32,6 +32,8 @@ (file-name-directory (or load-file-name default-directory)))) +(defvar eshell-test-value nil) + ;;; Tests: @@ -56,6 +58,76 @@ (should (equal (eshell-test-command-result "echo $\"user-login-name\"-foo") (concat user-login-name "-foo")))) +(ert-deftest esh-var-test/interp-var-indices () + "Interpolate list variable with indices" + (let ((eshell-test-value '("zero" "one" "two" "three" "four"))) + (should (equal (eshell-test-command-result "echo $eshell-test-value[0]") + "zero")) + (should (equal (eshell-test-command-result "echo $eshell-test-value[0 2]") + '("zero" "two"))) + (should (equal (eshell-test-command-result "echo $eshell-test-value[0 2 4]") + '("zero" "two" "four"))))) + +(ert-deftest esh-var-test/interp-var-split-indices () + "Interpolate string variable with indices" + (let ((eshell-test-value "zero one two three four")) + (should (equal (eshell-test-command-result "echo $eshell-test-value[0]") + "zero")) + (should (equal (eshell-test-command-result "echo $eshell-test-value[0 2]") + '("zero" "two"))) + (should (equal (eshell-test-command-result "echo $eshell-test-value[0 2 4]") + '("zero" "two" "four"))))) + +(ert-deftest esh-var-test/interp-var-string-split-indices () + "Interpolate string variable with string splitter and indices" + (let ((eshell-test-value "zero:one:two:three:four")) + (should (equal (eshell-test-command-result "echo $eshell-test-value[: 0]") + "zero")) + (should (equal (eshell-test-command-result "echo $eshell-test-value[: 0 2]") + '("zero" "two"))))) + +(ert-deftest esh-var-test/interp-var-regexp-split-indices () + "Interpolate string variable with regexp splitter and indices" + (let ((eshell-test-value "zero:one!two:three!four")) + (should (equal (eshell-test-command-result + "echo $eshell-test-value['[:!]' 0]") + "zero")) + (should (equal (eshell-test-command-result + "echo $eshell-test-value['[:!]' 0 2]") + '("zero" "two"))) + (should (equal (eshell-test-command-result + "echo $eshell-test-value[\"[:!]\" 0]") + "zero")) + (should (equal (eshell-test-command-result + "echo $eshell-test-value[\"[:!]\" 0 2]") + '("zero" "two"))))) + +(ert-deftest esh-var-test/interp-var-assoc () + "Interpolate alist variable with index" + (let ((eshell-test-value '(("foo" . 1)))) + (should (eq (eshell-test-command-result "echo $eshell-test-value[foo]") + 1)))) + +(ert-deftest esh-var-test/interp-var-length-list () + "Interpolate length of list variable" + (let ((eshell-test-value '((1 2) (3) (5 (6 7 8 9))))) + (should (eq (eshell-test-command-result "echo $#eshell-test-value") 3)) + (should (eq (eshell-test-command-result "echo $#eshell-test-value[1]") 1)) + (should (eq (eshell-test-command-result "echo $#eshell-test-value[2][1]") + 4)))) + +(ert-deftest esh-var-test/interp-var-length-string () + "Interpolate length of string variable" + (let ((eshell-test-value "foobar")) + (should (eq (eshell-test-command-result "echo $#eshell-test-value") 6)))) + +(ert-deftest esh-var-test/interp-var-length-alist () + "Interpolate length of alist variable" + (let ((eshell-test-value '(("foo" . (1 2 3))))) + (should (eq (eshell-test-command-result "echo $#eshell-test-value") 1)) + (should (eq (eshell-test-command-result "echo $#eshell-test-value[foo]") + 3)))) + (ert-deftest esh-var-test/interp-lisp () "Interpolate Lisp form evaluation" (should (equal (eshell-test-command-result "+ $(+ 1 2) 3") 6))) @@ -112,6 +184,86 @@ "echo \"hi, $\\\"user-login-name\\\"\"") (concat "hi, " user-login-name)))) +(ert-deftest esh-var-test/quoted-interp-var-indices () + "Interpolate string variable with indices inside double-quotes" + (let ((eshell-test-value '("zero" "one" "two" "three" "four"))) + (should (equal (eshell-test-command-result + "echo \"$eshell-test-value[0]\"") + "zero")) + (should (equal (eshell-test-command-result + "echo \"$eshell-test-value[0 2]\"") + '("zero" "two"))) + (should (equal (eshell-test-command-result + "echo \"$eshell-test-value[0 2 4]\"") + '("zero" "two" "four"))))) + +(ert-deftest esh-var-test/quoted-interp-var-split-indices () + "Interpolate string variable with indices inside double-quotes" + (let ((eshell-test-value "zero one two three four")) + (should (equal (eshell-test-command-result + "echo \"$eshell-test-value[0]\"") + "zero")) + (should (equal (eshell-test-command-result + "echo \"$eshell-test-value[0 2]\"") + '("zero" "two"))))) + +(ert-deftest esh-var-test/quoted-interp-var-string-split-indices () + "Interpolate string variable with string splitter and indices +inside double-quotes" + (let ((eshell-test-value "zero:one:two:three:four")) + (should (equal (eshell-test-command-result + "echo \"$eshell-test-value[: 0]\"") + "zero")) + (should (equal (eshell-test-command-result + "echo \"$eshell-test-value[: 0 2]\"") + '("zero" "two"))))) + +(ert-deftest esh-var-test/quoted-interp-var-regexp-split-indices () + "Interpolate string variable with regexp splitter and indices" + (let ((eshell-test-value "zero:one!two:three!four")) + (should (equal (eshell-test-command-result + "echo \"$eshell-test-value['[:!]' 0]\"") + "zero")) + (should (equal (eshell-test-command-result + "echo \"$eshell-test-value['[:!]' 0 2]\"") + '("zero" "two"))) + (should (equal (eshell-test-command-result + "echo \"$eshell-test-value[\\\"[:!]\\\" 0]\"") + "zero")) + (should (equal (eshell-test-command-result + "echo \"$eshell-test-value[\\\"[:!]\\\" 0 2]\"") + '("zero" "two"))))) + +(ert-deftest esh-var-test/quoted-interp-var-assoc () + "Interpolate alist variable with index inside double-quotes" + (let ((eshell-test-value '(("foo" . 1)))) + (should (equal (eshell-test-command-result + "echo \"$eshell-test-value[foo]\"") + 1)))) + +(ert-deftest esh-var-test/quoted-interp-var-length-list () + "Interpolate length of list variable inside double-quotes" + (let ((eshell-test-value '((1 2) (3) (5 (6 7 8 9))))) + (should (eq (eshell-test-command-result "echo \"$#eshell-test-value\"") 3)) + (should (eq (eshell-test-command-result "echo \"$#eshell-test-value[1]\"") + 1)) + (should (eq (eshell-test-command-result + "echo \"$#eshell-test-value[2][1]\"") + 4)))) + +(ert-deftest esh-var-test/quoted-interp-var-length-string () + "Interpolate length of string variable inside double-quotes" + (let ((eshell-test-value "foobar")) + (should (eq (eshell-test-command-result "echo \"$#eshell-test-value\"") + 6)))) + +(ert-deftest esh-var-test/quoted-interp-var-length-alist () + "Interpolate length of alist variable inside double-quotes" + (let ((eshell-test-value '(("foo" . (1 2 3))))) + (should (eq (eshell-test-command-result "echo \"$#eshell-test-value\"") 1)) + (should (eq (eshell-test-command-result "echo \"$#eshell-test-value[foo]\"") + 3)))) + (ert-deftest esh-var-test/quoted-interp-lisp () "Interpolate Lisp form evaluation inside double-quotes" (should (equal (eshell-test-command-result commit cccee7e840102488e01f9bb7c2220392d358f4f0 Author: Jim Porter Date: Sun Feb 27 21:04:30 2022 -0800 Fix Eshell dollar interpolation inside of double-quotes For example, echo "${echo hi}" previously tried to run the program named 'echo hi', instead of 'echo' with the argument 'hi'. * lisp/eshell/esh-arg.el (eshell-parse-inner-double-quote): New function. * lisp/eshell/esh-var.el (eshell-parse-variable-ref): Support parsing when wrapped in double-quiotes. * test/lisp/eshell/esh-var-tests.el (esh-var-test/interp-var) (esh-var-test/interp-quoted-var) (esh-var-test/interp-quoted-var-concat) (esh-var-test/quoted-interp-var) (esh-var-test/quoted-interp-quoted-var) (esh-var-test/quoted-interp-lisp, esh-var-test/quoted-interp-cmd) (esh-var-test/quoted-interp-temp-cmd): New tests. diff --git a/lisp/eshell/esh-arg.el b/lisp/eshell/esh-arg.el index 1a2f2a57e8..e19481c4ba 100644 --- a/lisp/eshell/esh-arg.el +++ b/lisp/eshell/esh-arg.el @@ -354,6 +354,30 @@ after are both returned." (list 'eshell-escape-arg arg)))) (goto-char (1+ end))))))) +(defun eshell-parse-inner-double-quote (bound) + "Parse the inner part of a double quoted string. +The string to parse starts at point and ends at BOUND. + +If Eshell is currently parsing a quoted string and there are any +backslash-escaped characters, this will return the unescaped +string, updating point to BOUND. Otherwise, this returns nil and +leaves point where it was." + (when eshell-current-quoted + (let (strings + (start (point)) + (special-char + (rx-to-string + `(seq "\\" (group (any ,@eshell-special-chars-inside-quoting)))))) + (while (re-search-forward special-char bound t) + (push (concat (buffer-substring start (match-beginning 0)) + (match-string 1)) + strings) + (setq start (match-end 0))) + (when strings + (push (buffer-substring start bound) strings) + (goto-char bound) + (apply #'concat (nreverse strings)))))) + (defun eshell-parse-special-reference () "Parse a special syntax reference, of the form `#'. diff --git a/lisp/eshell/esh-var.el b/lisp/eshell/esh-var.el index ee3ffbc647..24fdbde3cf 100644 --- a/lisp/eshell/esh-var.el +++ b/lisp/eshell/esh-var.el @@ -440,18 +440,16 @@ Possible options are: (let ((end (eshell-find-delimiter ?\{ ?\}))) (if (not end) (throw 'eshell-incomplete ?\{) + (forward-char) (prog1 `(eshell-convert (eshell-command-to-value (eshell-as-subcommand - ,(eshell-parse-command (cons (1+ (point)) end))))) + ,(let ((subcmd (or (eshell-parse-inner-double-quote end) + (cons (point) end))) + (eshell-current-quoted nil)) + (eshell-parse-command subcmd))))) (goto-char (1+ end)))))) - ((memq (char-after) '(?\' ?\")) - (let ((name (if (eq (char-after) ?\') - (eshell-parse-literal-quote) - (eshell-parse-double-quote)))) - (if name - `(eshell-get-variable ,(eval name) indices)))) ((eq (char-after) ?\<) (let ((end (eshell-find-delimiter ?\< ?\>))) (if (not end) @@ -463,7 +461,9 @@ Possible options are: `(let ((eshell-current-handles (eshell-create-handles ,temp 'overwrite))) (progn - (eshell-as-subcommand ,(eshell-parse-command cmd)) + (eshell-as-subcommand + ,(let ((eshell-current-quoted nil)) + (eshell-parse-command cmd))) (ignore (nconc eshell-this-command-hook ;; Quote this lambda; it will be evaluated @@ -478,9 +478,18 @@ Possible options are: (condition-case nil `(eshell-command-to-value (eshell-lisp-command - ',(read (current-buffer)))) + ',(read (or (eshell-parse-inner-double-quote (point-max)) + (current-buffer))))) (end-of-file (throw 'eshell-incomplete ?\()))) + ((looking-at (rx (or "'" "\"" "\\\""))) + (eshell-with-temp-command (or (eshell-parse-inner-double-quote (point-max)) + (cons (point) (point-max))) + (let ((name (if (eq (char-after) ?\') + (eshell-parse-literal-quote) + (eshell-parse-double-quote)))) + (when name + `(eshell-get-variable ,(eval name) indices))))) ((assoc (char-to-string (char-after)) eshell-variable-aliases-list) (forward-char) diff --git a/test/lisp/eshell/esh-var-tests.el b/test/lisp/eshell/esh-var-tests.el index 8d803e5ca4..7ec6a97519 100644 --- a/test/lisp/eshell/esh-var-tests.el +++ b/test/lisp/eshell/esh-var-tests.el @@ -37,6 +37,25 @@ ;; Variable interpolation +(ert-deftest esh-var-test/interp-var () + "Interpolate variable" + (should (equal (eshell-test-command-result "echo $user-login-name") + user-login-name))) + +(ert-deftest esh-var-test/interp-quoted-var () + "Interpolate quoted variable" + (should (equal (eshell-test-command-result "echo $'user-login-name'") + user-login-name)) + (should (equal (eshell-test-command-result "echo $\"user-login-name\"") + user-login-name))) + +(ert-deftest esh-var-test/interp-quoted-var-concat () + "Interpolate and concat quoted variable" + (should (equal (eshell-test-command-result "echo $'user-login-name'-foo") + (concat user-login-name "-foo"))) + (should (equal (eshell-test-command-result "echo $\"user-login-name\"-foo") + (concat user-login-name "-foo")))) + (ert-deftest esh-var-test/interp-lisp () "Interpolate Lisp form evaluation" (should (equal (eshell-test-command-result "+ $(+ 1 2) 3") 6))) @@ -79,6 +98,36 @@ (eshell-command-result-p "echo ${echo hi}-${*echo there}" "hi-there\n"))) +(ert-deftest esh-var-test/quoted-interp-var () + "Interpolate variable inside double-quotes" + (should (equal (eshell-test-command-result "echo \"$user-login-name\"") + user-login-name))) + +(ert-deftest esh-var-test/quoted-interp-quoted-var () + "Interpolate quoted variable inside double-quotes" + (should (equal (eshell-test-command-result + "echo \"hi, $'user-login-name'\"") + (concat "hi, " user-login-name))) + (should (equal (eshell-test-command-result + "echo \"hi, $\\\"user-login-name\\\"\"") + (concat "hi, " user-login-name)))) + +(ert-deftest esh-var-test/quoted-interp-lisp () + "Interpolate Lisp form evaluation inside double-quotes" + (should (equal (eshell-test-command-result + "echo \"hi $(concat \\\"the\\\" \\\"re\\\")\"") + "hi there"))) + +(ert-deftest esh-var-test/quoted-interp-cmd () + "Interpolate command result inside double-quotes" + (should (equal (eshell-test-command-result + "echo \"hi ${echo \\\"there\\\"}\"") + "hi there"))) + +(ert-deftest esh-var-test/quoted-interp-temp-cmd () + "Interpolate command result redirected to temp file inside double-quotes" + (should (equal (eshell-test-command-result "cat \"$\"") "hi"))) + ;; Built-in variables commit ae1acb601764009fc2551819f9193aa6e9441be4 Author: Jim Porter Date: Sat Feb 26 20:55:22 2022 -0800 Add a new macro to simplify parsing temporary Eshell command strings This abstracts out the somewhat-unusual "insert&delete" logic in 'eshell-parse-command' so that it can be used elsewhere, and also ensures that the deletion occurs even if an an error occurs. * lisp/eshell/esh-cmd.el (eshell-with-temp-command): New macro. (eshell-parse-command): Use it. diff --git a/lisp/eshell/esh-cmd.el b/lisp/eshell/esh-cmd.el index dceb061c8f..04b54d9d79 100644 --- a/lisp/eshell/esh-cmd.el +++ b/lisp/eshell/esh-cmd.el @@ -350,6 +350,36 @@ This only returns external (non-Lisp) processes." (defvar eshell--sep-terms) +(defmacro eshell-with-temp-command (command &rest body) + "Narrow the buffer to COMMAND and execute the forms in BODY. +COMMAND can either be a string, or a cons cell demarcating a +buffer region. If COMMAND is a string, temporarily insert it +into the buffer before narrowing. Point will be set to the +beginning of the narrowed region. + +The value returned is the last form in BODY." + (declare (indent 1)) + `(let ((cmd ,command)) + (if (stringp cmd) + ;; Since parsing relies partly on buffer-local state + ;; (e.g. that of `eshell-parse-argument-hook'), we need to + ;; perform the parsing in the Eshell buffer. + (let ((begin (point)) end + (inhibit-point-motion-hooks t)) + (with-silent-modifications + (insert cmd) + (setq end (point)) + (unwind-protect + (save-restriction + (narrow-to-region begin end) + (goto-char begin) + ,@body) + (delete-region begin end)))) + (save-restriction + (narrow-to-region (car cmd) (cdr cmd)) + (goto-char (car cmd)) + ,@body)))) + (defun eshell-parse-command (command &optional args toplevel) "Parse the COMMAND, adding ARGS if given. COMMAND can either be a string, or a cons cell demarcating a buffer @@ -361,15 +391,9 @@ hooks should be run before and after the command." (append (if (consp command) (eshell-parse-arguments (car command) (cdr command)) - (let ((here (point)) - (inhibit-point-motion-hooks t)) - (with-silent-modifications - ;; FIXME: Why not use a temporary buffer and avoid this - ;; "insert&delete" business? --Stef - (insert command) - (prog1 - (eshell-parse-arguments here (point)) - (delete-region here (point)))))) + (eshell-with-temp-command command + (goto-char (point-max)) + (eshell-parse-arguments (point-min) (point-max)))) args)) (commands (mapcar commit 7c7a4c26cbabe2d84d008e193b7db8ae106e9e47 Author: Jim Porter Date: Sun Feb 27 18:34:22 2022 -0800 Move Eshell variable interpolation tests to their own file * test/lisp/eshell/eshell-tests.el (eshell-test/interp-cmd) (eshell-test/interp-lisp, eshell-test/interp-temp-cmd) (eshell-test/interp-concat, eshell-test/interp-concat-lisp) (eshell-test/interp-concat2, eshell-test/interp-concat-lisp2) (eshell-test/interp-cmd-external) (eshell-test/interp-cmd-external-concat, eshell-test/window-height) (eshell-test/window-width, eshell-test/last-result-var) (eshell-test/last-result-var2, eshell-test/last-arg-var): Move from here... * test/lisp/eshell/esh-var-test.el (esh-var-test/interp-lisp) (esh-var-test/interp-cmd, esh-var-test/interp-cmd-external) (esh-var-test/interp-temp-cmd, esh-var-test/interp-concat-lisp) (esh-var-test/interp-concat-lisp2, esh-var-test/interp-concat-cmd) (esh-var-test/interp-concat-cmd2) (esh-var-test/interp-concat-cmd-external, esh-var-test/window-height) (esh-var-test/window-width, esh-var-test/last-result-var) (esh-var-test/last-result-var2, esh-var-test/last-arg-var): ... to here. diff --git a/test/lisp/eshell/esh-var-tests.el b/test/lisp/eshell/esh-var-tests.el new file mode 100644 index 0000000000..8d803e5ca4 --- /dev/null +++ b/test/lisp/eshell/esh-var-tests.el @@ -0,0 +1,111 @@ +;;; esh-var-tests.el --- esh-var test suite -*- lexical-binding:t -*- + +;; Copyright (C) 2022 Free Software Foundation, Inc. + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see . + +;;; Commentary: + +;; Tests for Eshell's variable handling. + +;;; Code: + +(require 'ert) +(require 'esh-mode) +(require 'eshell) + +(require 'eshell-tests-helpers + (expand-file-name "eshell-tests-helpers" + (file-name-directory (or load-file-name + default-directory)))) + +;;; Tests: + + +;; Variable interpolation + +(ert-deftest esh-var-test/interp-lisp () + "Interpolate Lisp form evaluation" + (should (equal (eshell-test-command-result "+ $(+ 1 2) 3") 6))) + +(ert-deftest esh-var-test/interp-cmd () + "Interpolate command result" + (should (equal (eshell-test-command-result "+ ${+ 1 2} 3") 6))) + +(ert-deftest esh-var-test/interp-cmd-external () + "Interpolate command result from external command" + (skip-unless (executable-find "echo")) + (with-temp-eshell + (eshell-command-result-p "echo ${*echo hi}" + "hi\n"))) + +(ert-deftest esh-var-test/interp-temp-cmd () + "Interpolate command result redirected to temp file" + (should (equal (eshell-test-command-result "cat $") "hi"))) + +(ert-deftest esh-var-test/interp-concat-lisp () + "Interpolate and concat Lisp form" + (should (equal (eshell-test-command-result "+ $(+ 1 2)3 3") 36))) + +(ert-deftest esh-var-test/interp-concat-lisp2 () + "Interpolate and concat two Lisp forms" + (should (equal (eshell-test-command-result "+ $(+ 1 2)$(+ 1 2) 3") 36))) + +(ert-deftest esh-var-test/interp-concat-cmd () + "Interpolate and concat command" + (should (equal (eshell-test-command-result "+ ${+ 1 2}3 3") 36))) + +(ert-deftest esh-var-test/interp-concat-cmd2 () + "Interpolate and concat two commands" + (should (equal (eshell-test-command-result "+ ${+ 1 2}${+ 1 2} 3") 36))) + +(ert-deftest esh-var-test/interp-concat-cmd-external () + "Interpolate command result from external command with concatenation" + (skip-unless (executable-find "echo")) + (with-temp-eshell + (eshell-command-result-p "echo ${echo hi}-${*echo there}" + "hi-there\n"))) + + +;; Built-in variables + +(ert-deftest esh-var-test/window-height () + "$LINES should equal (window-height)" + (should (eshell-test-command-result "= $LINES (window-height)"))) + +(ert-deftest esh-var-test/window-width () + "$COLUMNS should equal (window-width)" + (should (eshell-test-command-result "= $COLUMNS (window-width)"))) + +(ert-deftest esh-var-test/last-result-var () + "Test using the \"last result\" ($$) variable" + (with-temp-eshell + (eshell-command-result-p "+ 1 2; + $$ 2" + "3\n5\n"))) + +(ert-deftest esh-var-test/last-result-var2 () + "Test using the \"last result\" ($$) variable twice" + (with-temp-eshell + (eshell-command-result-p "+ 1 2; + $$ $$" + "3\n6\n"))) + +(ert-deftest esh-var-test/last-arg-var () + "Test using the \"last arg\" ($_) variable" + (with-temp-eshell + (eshell-command-result-p "+ 1 2; + $_ 4" + "3\n6\n"))) + +;; esh-var-tests.el ends here diff --git a/test/lisp/eshell/eshell-tests.el b/test/lisp/eshell/eshell-tests.el index eff4edd62c..e31db07c61 100644 --- a/test/lisp/eshell/eshell-tests.el +++ b/test/lisp/eshell/eshell-tests.el @@ -85,48 +85,6 @@ Test that trailing arguments outside the subcommand are ignored. e.g. \"{(+ 1 2)} 3\" => 3" (should (equal (eshell-test-command-result "{(+ 1 2)} 3") 3))) -(ert-deftest eshell-test/interp-cmd () - "Interpolate command result" - (should (equal (eshell-test-command-result "+ ${+ 1 2} 3") 6))) - -(ert-deftest eshell-test/interp-lisp () - "Interpolate Lisp form evaluation" - (should (equal (eshell-test-command-result "+ $(+ 1 2) 3") 6))) - -(ert-deftest eshell-test/interp-temp-cmd () - "Interpolate command result redirected to temp file" - (should (equal (eshell-test-command-result "cat $") "hi"))) - -(ert-deftest eshell-test/interp-concat () - "Interpolate and concat command" - (should (equal (eshell-test-command-result "+ ${+ 1 2}3 3") 36))) - -(ert-deftest eshell-test/interp-concat-lisp () - "Interpolate and concat Lisp form" - (should (equal (eshell-test-command-result "+ $(+ 1 2)3 3") 36))) - -(ert-deftest eshell-test/interp-concat2 () - "Interpolate and concat two commands" - (should (equal (eshell-test-command-result "+ ${+ 1 2}${+ 1 2} 3") 36))) - -(ert-deftest eshell-test/interp-concat-lisp2 () - "Interpolate and concat two Lisp forms" - (should (equal (eshell-test-command-result "+ $(+ 1 2)$(+ 1 2) 3") 36))) - -(ert-deftest eshell-test/interp-cmd-external () - "Interpolate command result from external command" - (skip-unless (executable-find "echo")) - (with-temp-eshell - (eshell-command-result-p "echo ${*echo hi}" - "hi\n"))) - -(ert-deftest eshell-test/interp-cmd-external-concat () - "Interpolate command result from external command with concatenation" - (skip-unless (executable-find "echo")) - (with-temp-eshell - (eshell-command-result-p "echo ${echo hi}-${*echo there}" - "hi-there\n"))) - (ert-deftest eshell-test/pipe-headproc () "Check that piping a non-process to a process command waits for the process" (skip-unless (executable-find "cat")) @@ -152,32 +110,6 @@ e.g. \"{(+ 1 2)} 3\" => 3" (eshell-wait-for-subprocess) (eshell-match-result "OLLEH\n"))) -(ert-deftest eshell-test/window-height () - "$LINES should equal (window-height)" - (should (eshell-test-command-result "= $LINES (window-height)"))) - -(ert-deftest eshell-test/window-width () - "$COLUMNS should equal (window-width)" - (should (eshell-test-command-result "= $COLUMNS (window-width)"))) - -(ert-deftest eshell-test/last-result-var () - "Test using the \"last result\" ($$) variable" - (with-temp-eshell - (eshell-command-result-p "+ 1 2; + $$ 2" - "3\n5\n"))) - -(ert-deftest eshell-test/last-result-var2 () - "Test using the \"last result\" ($$) variable twice" - (with-temp-eshell - (eshell-command-result-p "+ 1 2; + $$ $$" - "3\n6\n"))) - -(ert-deftest eshell-test/last-arg-var () - "Test using the \"last arg\" ($_) variable" - (with-temp-eshell - (eshell-command-result-p "+ 1 2; + $_ 4" - "3\n6\n"))) - (ert-deftest eshell-test/inside-emacs-var () "Test presence of \"INSIDE_EMACS\" in subprocesses" (with-temp-eshell commit 92e2d19fe787ce73db15d1549880b54743c0d929 Author: Eli Zaretskii Date: Thu Mar 3 15:53:04 2022 +0200 One more fix of the BPA implementation * src/bidi.c (bidi_find_bracket_pairs): Disable BPA optimization when there are no strong directional characters inside the bracketed pair. (Bug#54219) diff --git a/src/bidi.c b/src/bidi.c index 5f47d9e9a7..a548960048 100644 --- a/src/bidi.c +++ b/src/bidi.c @@ -2758,6 +2758,7 @@ bidi_find_bracket_pairs (struct bidi_it *bidi_it) (which requires the display engine to copy the cache back and forth many times). */ if (maxlevel == base_level + && (l2r_seen || r2l_seen) /* N0d */ && ((base_level == 0 && !r2l_seen) || (base_level == 1 && !l2r_seen))) { commit cd51d9c7ab5914fb58cbba6ae7bf5d53f7fef03f Author: Eli Zaretskii Date: Thu Mar 3 14:46:20 2022 +0200 Fix handling of brackets in BPA * src/bidi.c (bidi_resolve_brackets): Fix implementation of UBA's N0 rule when there are no strong directional characters inside the bracketed pair. (Bug#54219) diff --git a/src/bidi.c b/src/bidi.c index 30a3be6c94..5f47d9e9a7 100644 --- a/src/bidi.c +++ b/src/bidi.c @@ -2924,7 +2924,8 @@ bidi_resolve_brackets (struct bidi_it *bidi_it) eassert (bidi_it->bracket_pairing_pos > bidi_it->charpos); if (bidi_it->bracket_enclosed_type == embedding_type) /* N0b */ type = embedding_type; - else + else if (bidi_it->bracket_enclosed_type == STRONG_L /* N0c, N0d */ + || bidi_it->bracket_enclosed_type == STRONG_R) { switch (bidi_it->prev_for_neutral.type) { @@ -2944,6 +2945,7 @@ bidi_resolve_brackets (struct bidi_it *bidi_it) break; default: /* N0d: Do not set the type for that bracket pair. */ + /* (Actuallly, this shouldn't happen.) */ break; } }