commit 85f2bf2bc7ba0a6557e992601658530f562e00d2 (HEAD, refs/remotes/origin/master) Author: Paul Eggert Date: Thu Nov 27 23:33:36 2025 -0800 Port better to GNUstep with Nix gcc Problem reported by David Caldwell (bug#79879). * configure.ac: When configuring for GNUstep and the Objective C compiler does not support even C99 features, use the first flag of -std=gnu23, -std=gnu17, -std=gnu11, -std=gnu99 that works, instead of insisting on -std=c99 which (a) is too strict and (b) can override another -std= option in an undesirable way. diff --git a/configure.ac b/configure.ac index db4cc277e48..39f1fb10091 100644 --- a/configure.ac +++ b/configure.ac @@ -2957,16 +2957,24 @@ if test "${HAVE_NS}" = yes; then fi AC_CACHE_CHECK( - [if the Objective C compiler defaults to C99], - [emacs_cv_objc_c99], - [AC_COMPILE_IFELSE( - [AC_LANG_PROGRAM([], [[for (int i = 0;;);]])], - [emacs_cv_objc_c99=yes], - [emacs_cv_objc_c99=no])]) + [for C99-or-better flag for Objective C compiler], + [emacs_cv_objc_c99_or_better], + [save_OBJCFLAGS=$OBJCFLAGS + for emacs_cv_objc_c99_or_better in \ + 'none needed' -std=gnu23 -std=gnu17 -std=gnu11 -std=gnu99 '' + do + AS_CASE([$emacs_cv_objc_c99_or_better], + ['none needed'], [], + [-*], [OBJCFLAGS="$save_OBJCFLAGS $emacs_cv_objc_c99_or_better"], + [continue]) + AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([], [[for (int i = 0;;);]])], + [break]) + done + OBJCFLAGS=$save_OBJCFLAGS]) + AS_CASE([$emacs_cv_objc_c99_or_better], + [-*], [GNU_OBJC_CFLAGS="$GNU_OBJC_CFLAGS $emacs_cv_objc_c99_or_better"]) - if test x$emacs_cv_objc_c99 = xno ; then - GNU_OBJC_CFLAGS="$GNU_OBJC_CFLAGS -std=c99" - fi AC_LANG_POP([Objective C]) fi commit a1ab5f429daa7484874f9c76ecb9add7e93756e2 Author: Juri Linkov Date: Fri Nov 28 09:23:57 2025 +0200 * lisp/cus-start.el: Make 'treesit-extra-load-path' customizable. * lisp/treesit.el (treesit-auto-install-grammar): Add the value 'ask-dir'. (treesit-ensure-installed): When 'treesit-auto-install-grammar' is 'ask' or 'ask-dir', ask for the directory to install the grammar library using the first writable directory of 'treesit-extra-load-path' as default. Also add the provided directory to 'treesit-extra-load-path' afterwards. * src/treesit.c (treesit-extra-load-path): In the docstring mention the fact that the first directory is special (bug#79862). diff --git a/etc/NEWS b/etc/NEWS index 18b2be9cb8c..ef2c54efcde 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -924,6 +924,11 @@ It controls the automatic installation of tree-sitter grammar libraries needed for tree-sitter based modes, if these grammar libraries are not available when such modes are turned on. +*** 'treesit-extra-load-path' now is a customizable user option. +The first directory in the list is used as the default directory +to install the language grammar when 'treesit-auto-install-grammar' +is 'ask' or 'ask-dir'. + *** 'treesit-language-source-alist' supports keywords. The language and URL are mandatory, but remaining data can use keywords: '(json "https://github.com/tree-sitter/tree-sitter-json" :commit "4d770d3")'. diff --git a/lisp/cus-start.el b/lisp/cus-start.el index 19ac478e21a..b59decbc1a9 100644 --- a/lisp/cus-start.el +++ b/lisp/cus-start.el @@ -621,6 +621,9 @@ Leaving \"Default\" unchecked is equivalent with specifying a default of (const :tag "Default" nil) (const :tag "Silent" ignore) function)) + ;; treesit.c + (treesit-extra-load-path + treesit (repeat (directory :format "%v"))) ;; undo.c (undo-limit undo integer "27.1") (undo-strong-limit undo integer "27.1") diff --git a/lisp/treesit.el b/lisp/treesit.el index e365fc218dd..b5cb01d03d3 100644 --- a/lisp/treesit.el +++ b/lisp/treesit.el @@ -5461,8 +5461,12 @@ The copied query files are queries/highlights.scm." "Whether to install tree-sitter language grammar libraries when needed. This controls whether Emacs will install missing grammar libraries when they are needed by some tree-sitter based mode. -If `ask', ask for confirmation before installing the required grammar library. If `always', install the grammar library without asking. +If `ask', ask for confirmation before installing the required grammar library. +If `ask-dir', ask for confirmation and also for a directory name where +to install the grammar library. The directory name is also asked when +the value is `ask' and `treesit-extra-load-path' is customized to a list +of directories. If nil or `never' or anything else, don't install the grammar library even while visiting a file in the mode that requires such grammar; this might display a warning and/or fail to turn on the mode." @@ -5470,7 +5474,9 @@ might display a warning and/or fail to turn on the mode." (const :tag "Always automatically install grammar libraries" always) (const :tag "Ask whether to install missing grammar libraries" - ask)) + ask) + (const :tag "Ask where to install missing grammar libraries" + ask-dir)) :version "31.1") (defun treesit-ensure-installed (lang) @@ -5479,14 +5485,28 @@ The option `treesit-auto-install-grammar' defines whether to install the grammar library if it's unavailable." (when (treesit-available-p) (or (treesit-ready-p lang t) - (when (or (eq treesit-auto-install-grammar 'always) - (and (eq treesit-auto-install-grammar 'ask) - (y-or-n-p (format "\ -Tree-sitter grammar for `%s' is missing; install it?" - lang)))) - (treesit-install-language-grammar lang) - ;; Check that the grammar was installed successfully - (treesit-ready-p lang))))) + (let (out-dir) + (when (or (eq treesit-auto-install-grammar 'always) + (and (memq treesit-auto-install-grammar '(ask ask-dir)) + (y-or-n-p (format "\ +Tree-sitter grammar for `%s' is missing; install it?" lang)) + (or (and (eq treesit-auto-install-grammar 'ask) + ;; Still ask dir for customized path + (null treesit-extra-load-path)) + (let ((default-out-dir + (or (seq-find #'file-writable-p + treesit-extra-load-path) + (locate-user-emacs-file "tree-sitter")))) + (setq out-dir (read-directory-name + (format-prompt "\ +Install grammar for `%s' to" nil lang) + default-out-dir + treesit-extra-load-path t)) + (add-to-list 'treesit-extra-load-path out-dir) + t)))) + (treesit-install-language-grammar lang out-dir) + ;; Check that the grammar was installed successfully + (treesit-ready-p lang)))))) ;;; Treesit enabled modes diff --git a/src/treesit.c b/src/treesit.c index 3230d0a50a1..d3b6f36ec81 100644 --- a/src/treesit.c +++ b/src/treesit.c @@ -5273,7 +5273,9 @@ The value should be a list of directories. When trying to load a tree-sitter language definition, Emacs first looks in the directories mentioned in this variable, then in the `tree-sitter' subdirectory of `user-emacs-directory', and -then in the system default locations for dynamic libraries, in that order. */); +then in the system default locations for dynamic libraries, in that order. +The first writeable directory in the list is special: it's used as the +default directory when automatically installing the language grammar. */); Vtreesit_extra_load_path = Qnil; DEFVAR_LISP ("treesit-thing-settings", commit a4c5e64b110edd344da270c521a9f485860acfcd Author: Eli Zaretskii Date: Fri Nov 28 08:45:02 2025 +0200 ; Rename a variable. diff --git a/lisp/misearch.el b/lisp/misearch.el index 72e544ebf0e..4cf749da7ea 100644 --- a/lisp/misearch.el +++ b/lisp/misearch.el @@ -406,7 +406,7 @@ modified buffer to be able to use unsaved changes." (declare-function diff-setup-whitespace "diff-mode" ()) (declare-function diff-setup-buffer-type "diff-mode" ()) -(defvar coding-system--for-buffer-diff) ; from diff.el +(defvar diff--coding-system-for-buffer) ; from diff.el ;;;###autoload (defun multi-file-replace-as-diff (files from-string replacements regexp-flag delimited-flag) @@ -445,7 +445,7 @@ as in `perform-replace'." ;; Make sure any supported characters can be written to a ;; file without asking the user to select a safe ;; coding-system. - (coding-system--for-buffer-diff 'utf-8-emacs)) + (diff--coding-system-for-buffer 'utf-8-emacs)) (when non-file-buffer (setq file-name (buffer-name file-name))) (when (or file-exists file-buffer) (with-temp-buffer @@ -548,7 +548,7 @@ specify labels to use for file names." " "))) (with-current-buffer buf (let ((inhibit-read-only t) - (coding-system-for-read (or coding-system--for-buffer-diff + (coding-system-for-read (or diff--coding-system-for-buffer coding-system-for-read))) (insert command "\n") (call-process shell-file-name nil buf nil diff --git a/lisp/vc/diff.el b/lisp/vc/diff.el index e2148328ad9..d85b4487508 100644 --- a/lisp/vc/diff.el +++ b/lisp/vc/diff.el @@ -118,7 +118,7 @@ Non-interactively, OLD and NEW may each be a file or a buffer." (display-buffer (diff-no-select old new switches no-async))) -(defvar coding-system--for-buffer-diff nil +(defvar diff--coding-system-for-buffer nil "Used to pass buffer text encoding from `multi-file-diff-no-select'.") (defun diff-file-local-copy (file-or-buf) @@ -128,7 +128,7 @@ temporary file with the buffer's contents." (if (bufferp file-or-buf) (with-current-buffer file-or-buf (let ((tempfile (make-temp-file "buffer-content-")) - (coding-system-for-write (or coding-system--for-buffer-diff + (coding-system-for-write (or diff--coding-system-for-buffer coding-system-for-write))) (if diff-entire-buffers (write-region nil nil tempfile nil 'nomessage) commit 9b4d9bb01e5f93ac0060aaa2579f687e9157ef4a Author: Paul Eggert Date: Thu Nov 27 10:38:53 2025 -0800 Fix recently-introduced ash bug Problem reported by John Paul Adrian Glaubitz (bug#79876). * src/data.c (Fash): Don’t assume stdc_leading_zeros, which returns an unsigned integer of unspecified width, returns a value narrower than EMACS_INT. Also, don’t munge the code to worry about stdc_leading_zeros (0); unlike GCC’s __builtin_clz, stdc_leading_zeros works fine on 0. And use a temporary to avoid a cast. diff --git a/src/data.c b/src/data.c index fb9e1e3218d..a1861bd53d0 100644 --- a/src/data.c +++ b/src/data.c @@ -3532,10 +3532,10 @@ discarding bits. */) CHECK_INTEGER (value); CHECK_INTEGER (count); - if (BASE_EQ (value, make_fixnum (0))) - return value; if (! FIXNUMP (count)) { + if (BASE_EQ (value, make_fixnum (0))) + return value; if (mpz_sgn (*xbignum_val (count)) < 0) { EMACS_INT v = (FIXNUMP (value) ? XFIXNUM (value) @@ -3563,8 +3563,9 @@ discarding bits. */) else if (FIXNUMP (value)) { EMACS_INT v = XFIXNUM (value); - if (stdc_leading_zeros ((EMACS_UINT)(v < 0 ? ~v : v)) - c - >= EMACS_INT_WIDTH - FIXNUM_BITS + 1) + EMACS_UINT uv = v < 0 ? ~v : v; + EMACS_INT lz = stdc_leading_zeros (uv); + if (EMACS_INT_WIDTH - FIXNUM_BITS < lz - c) return make_fixnum (v << c); } commit e576dc7556994b3fb76bc0092ec828c0026de327 Author: Spencer Baugh Date: Tue Nov 18 13:28:29 2025 -0500 Support completion-eager-display in completing-read-multiple Make completing-read-multiple do eager display of *Completions* when the table requests it. As a side-effect of the implementation, we now check again if eager-display is enabled if we have to retry doing eager-display due to being interrupted by user input. This is mildly nicer since it gives the completion table a little more control: for example, maybe the table only wants to do eager-display if the minibuffer is empty; this change makes that work better. * lisp/minibuffer.el (completions--start-eager-display) (completing-read-default): Move the code for checking whether to do eager-display into completions--start-eager-display. * lisp/emacs-lisp/crm.el (completing-read-multiple): Call completions--start-eager-display (bug#79858). diff --git a/lisp/emacs-lisp/crm.el b/lisp/emacs-lisp/crm.el index 425a606cb12..f1e5e4fc63e 100644 --- a/lisp/emacs-lisp/crm.el +++ b/lisp/emacs-lisp/crm.el @@ -285,7 +285,8 @@ with empty strings removed." (unless (eq require-match t) require-match)) (setq-local minibuffer--require-match require-match) (setq-local minibuffer--original-buffer buffer) - (setq-local crm-completion-table table)) + (setq-local crm-completion-table table) + (completions--start-eager-display)) (setq input (read-from-minibuffer (format-spec crm-prompt diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el index 43fe2dd196d..6e2dce2844f 100644 --- a/lisp/minibuffer.el +++ b/lisp/minibuffer.el @@ -2767,9 +2767,19 @@ so that the update is less likely to interfere with user typing." (completions--start-eager-display)))) (defun completions--start-eager-display () - "Display the *Completions* buffer when the user is next idle." - (setq completion-eager-display--timer - (run-with-idle-timer 0 nil #'completions--eager-display))) + "Maybe display the *Completions* buffer when the user is next idle. + +Only displays if `completion-eager-display' is t, or if eager display +has been requested by the completion table." + (when completion-eager-display + (when (or (eq completion-eager-display t) + (completion-metadata-get + (completion-metadata + (buffer-substring-no-properties (minibuffer-prompt-end) (point)) + minibuffer-completion-table minibuffer-completion-predicate) + 'eager-display)) + (setq completion-eager-display--timer + (run-with-idle-timer 0 nil #'completions--eager-display))))) (defun completions--post-command-update () "Update displayed *Completions* buffer after command, once." @@ -5159,18 +5169,7 @@ See `completing-read' for the meaning of the arguments." (setq-local minibuffer--original-buffer buffer) ;; Copy the value from original buffer to the minibuffer. (setq-local completion-ignore-case c-i-c) - ;; Show the completion help eagerly if - ;; `completion-eager-display' is t or if eager display - ;; has been requested by the completion table. - (when completion-eager-display - (when (or (eq completion-eager-display t) - (completion-metadata-get - (completion-metadata - (buffer-substring-no-properties - (minibuffer-prompt-end) (point)) - collection predicate) - 'eager-display)) - (completions--start-eager-display)))) + (completions--start-eager-display)) (read-from-minibuffer prompt initial-input keymap nil hist def inherit-input-method)))) (when (and (equal result "") def) commit 43bcf3c43b287d5bc6ee7ce5fc508bc4bdfd371b Author: Stefan Monnier Date: Wed Nov 26 12:19:25 2025 -0500 (display-buffer-overriding-action): Try and discourage abuse * lisp/window.el (display-buffer-overriding-action): Document the intended use in the deluded hope to reduce the current abuse. diff --git a/lisp/window.el b/lisp/window.el index af5d73d3384..6a35e553bb9 100644 --- a/lisp/window.el +++ b/lisp/window.el @@ -8026,9 +8026,14 @@ See `display-buffer' for details. This variable is not intended for user customization. Lisp programs should never set this variable permanently but may bind it around calls of buffer display functions like `display-buffer' -or `pop-to-buffer'. Since such a binding will affect any nested +or `pop-to-buffer'. + +Since such a binding will affect unconditionally any nested buffer display requests, this variable should be used with utmost -care.") +care, typically in response to an explicit request by the user. +Also, any code that sets this variable needs to interact nicely with +other code that sets this variable. +See `other-frame-prefix' for an example of use.") (put 'display-buffer-overriding-action 'risky-local-variable t) (defcustom display-buffer-alist nil commit 4b4b48d019f146242184829ec222855432c959a5 Merge: b6163d42d26 77f45c5db8b Author: Eli Zaretskii Date: Thu Nov 27 13:43:17 2025 +0200 Merge branch 'master' of git.savannah.gnu.org:/srv/git/emacs commit b6163d42d26300d4bfd003668da4049a7e2e2e94 Author: Eli Zaretskii Date: Thu Nov 27 13:42:01 2025 +0200 Improve the fix for 'd' command in 'query-replace' * lisp/misearch.el (coding-system--for-buffer-diff): Move definition from here... * lisp/vc/diff.el (coding-system--for-buffer-diff): ...to here. diff --git a/lisp/misearch.el b/lisp/misearch.el index 28cda1fcbf0..72e544ebf0e 100644 --- a/lisp/misearch.el +++ b/lisp/misearch.el @@ -406,8 +406,7 @@ modified buffer to be able to use unsaved changes." (declare-function diff-setup-whitespace "diff-mode" ()) (declare-function diff-setup-buffer-type "diff-mode" ()) -(defvar coding-system--for-buffer-diff nil - "Used to pass encoding down to callees of `multi-file-diff-no-select'.") +(defvar coding-system--for-buffer-diff) ; from diff.el ;;;###autoload (defun multi-file-replace-as-diff (files from-string replacements regexp-flag delimited-flag) @@ -519,6 +518,7 @@ variable `diff-switches' are passed to the diff command, otherwise SWITCHES is used. SWITCHES can be a string or a list of strings. BUF should be non-nil. LABEL-OLD and LABEL-NEW specify labels to use for file names." + (require 'diff) (unless (bufferp new) (setq new (expand-file-name new))) (unless (bufferp old) (setq old (expand-file-name old))) (or switches (setq switches diff-switches)) ; If not specified, use default. diff --git a/lisp/vc/diff.el b/lisp/vc/diff.el index 9e4a5349fef..e2148328ad9 100644 --- a/lisp/vc/diff.el +++ b/lisp/vc/diff.el @@ -118,7 +118,8 @@ Non-interactively, OLD and NEW may each be a file or a buffer." (display-buffer (diff-no-select old new switches no-async))) -(defvar coding-system--for-buffer-diff) ; from misearch.el +(defvar coding-system--for-buffer-diff nil + "Used to pass buffer text encoding from `multi-file-diff-no-select'.") (defun diff-file-local-copy (file-or-buf) "Like `file-local-copy' but also supports a buffer as the argument. commit 77f45c5db8b440e526f31f83cbd5b67702863209 Author: Sean Whitton Date: Thu Nov 27 10:55:06 2025 +0000 * lisp/vc/vc.el (vc--apply-to-other-working-tree): Use y-or-n-p. diff --git a/lisp/vc/vc.el b/lisp/vc/vc.el index 9b3e62d0a86..6149aca302d 100644 --- a/lisp/vc/vc.el +++ b/lisp/vc/vc.el @@ -5280,7 +5280,7 @@ determine the changes to copy or move. MOVE non-nil means to move instead of copy." (unless (or (not move) vc-no-confirm-moving-changes - (yes-or-no-p + (y-or-n-p (format "Really %s uncommitted work out of this working tree?" (propertize "move" 'face 'bold)))) (user-error "Aborted")) commit 702b16f999c1e919c2fb88d648e040775d688a0a Author: Dmitry Gutov Date: Thu Nov 27 12:26:55 2025 +0200 Fix project-prompt-project-name with (choose a dir) * lisp/progmodes/project.el (project-prompt-project-name): Use correct equality check (bug#79894). diff --git a/lisp/progmodes/project.el b/lisp/progmodes/project.el index 1c510f34b40..a6d8cb0060a 100644 --- a/lisp/progmodes/project.el +++ b/lisp/progmodes/project.el @@ -2279,8 +2279,8 @@ If ALLOW-EMPTY is non-nil, it is possible to exit with no input." while (and (not allow-empty) (equal pr-name ""))) (pcase pr-name ("" "") - ('dir-choice (read-directory-name "Select directory: " - default-directory nil t)) + ((pred (equal dir-choice)) (read-directory-name "Select directory: " + default-directory nil t)) (_ (let ((proj (assoc pr-name choices))) (if (stringp proj) proj (project-root (cdr proj)))))))) commit 26bd33c853d6d40a72fd7f76ed6d4612f4daf956 Author: Neal Sidhwaney Date: Thu Nov 6 14:33:07 2025 -0500 Add test cases for 'dolist' This patch adds tests for the following scenarios: - Every element is handled (basic functionality) - Result specified in SPEC is returned - Bindings named "tail" are not shadowed by 'dolist' * test/lisp/subr-tests.el (subr-tests--dolist--every-element-is-handled) (subr-tests--dolist--returns-spec-result) (subr-tests--dolist--does-not-shadow-tail-binding): New tests. (Bug#79778) diff --git a/test/lisp/subr-tests.el b/test/lisp/subr-tests.el index fc980eae596..47ff8b6da01 100644 --- a/test/lisp/subr-tests.el +++ b/test/lisp/subr-tests.el @@ -762,6 +762,28 @@ cf. Bug#25477." (should-error (eval '(dolist "foo") lb) :type 'wrong-type-argument))) +(ert-deftest subr-tests--dolist--every-element-is-handled () + "Test that `dolist' processes each element of a list in order." + (let ((expected-elements '(1 2 3 4))) + (dolist (x '(1 2 3 4)) + (should (equal x (pop expected-elements)))))) + +(ert-deftest subr-tests--dolist--returns-spec-result () + "Test that `dolist' returns result specified in SPEC." + (let ((dolist-result (dolist (x '(1 2 3 4) t) + x)) + (dolist-no-result (dolist (x '(1 2 3 4)) + x))) + (should (equal dolist-result t)) + (should (equal dolist-no-result nil)))) + +(ert-deftest subr-tests--dolist--does-not-shadow-tail-binding () + "Test that `dolist` does not shadow bindings named `tail'" + (let ((tail 0)) + (dolist (x '(1 2 3 4)) + (setq tail (+ tail x))) + (should (equal tail 10)))) + (ert-deftest subr-tests-bug22027 () "Test for https://debbugs.gnu.org/22027 ." (let ((default "foo") res) commit 1ddc28997b75ff4eaa8d04171764eeb6d8d86922 Author: Paul Eggert Date: Thu Nov 27 00:39:17 2025 -0800 Work around a bug in GCC 14 i386 C23 alignas Problem reported by Helmut Eller (bug#79886). * src/lisp.h (USE_LSB_TAG): Adjust to alignas changes. (alignas): For GCC and Clang, prefer __attribute__ ((__aligned__ (A))) to alignas (A) even if the compiler claims to support the latter. This works around a bug in GCC 14 i386 C23 alignas. No need to worry about whether __alignas_is_defined is defined. diff --git a/src/lisp.h b/src/lisp.h index 0d457345d85..47442a29f50 100644 --- a/src/lisp.h +++ b/src/lisp.h @@ -253,10 +253,10 @@ DEFINE_GDB_SYMBOL_END (INTTYPEBITS) So, USE_LSB_TAG is true only on hosts where it might be useful. */ DEFINE_GDB_SYMBOL_BEGIN (bool, USE_LSB_TAG) #if (ALIGNOF_EMACS_INT < IDEAL_GCALIGNMENT && !defined alignas \ + && ! (__GNUC__ || 4 <= __clang_major__) \ + && __STDC_VERSION__ < 202311 && __cplusplus < 201103) \ && !defined WIDE_EMACS_INT \ - && !defined HAVE_STRUCT_ATTRIBUTE_ALIGNED \ - && !defined __alignas_is_defined \ - && __STDC_VERSION__ < 202311 && __cplusplus < 201103) + && !defined HAVE_STRUCT_ATTRIBUTE_ALIGNED #define USE_LSB_TAG 0 #else /* ALIGNOF_EMACS_INT >= IDEAL_GCALIGNMENT || defined alignas ... */ #define USE_LSB_TAG (VAL_MAX / 2 < INTPTR_MAX) @@ -268,10 +268,14 @@ DEFINE_GDB_SYMBOL_BEGIN (EMACS_INT, VALMASK) # define VALMASK (USE_LSB_TAG ? - (1 << GCTYPEBITS) : VAL_MAX) DEFINE_GDB_SYMBOL_END (VALMASK) -/* Ignore 'alignas' on compilers lacking it. */ -#if (!defined alignas && !defined __alignas_is_defined \ - && __STDC_VERSION__ < 202311 && __cplusplus < 201103) -# define alignas(a) +/* Support 'alignas (A)' if possible, where A is an integer constant. */ +#ifndef alignas +# if __GNUC__ || 4 <= __clang_major__ +/* This is more reliable than the alignas operator, in GCC 14. */ +# define alignas(a) __attribute__ ((__aligned__ (a))) +# elif __STDC_VERSION__ < 202311 && __cplusplus < 201103 +# define alignas(a) /* not supported */ +# endif #endif /* The minimum alignment requirement for Lisp objects that is imposed by the commit 18a346da6ac0eaa28f835d66378ef023b7f5a1ab Author: Pranshu Sharma Date: Thu Nov 27 09:34:14 2025 +0100 Add new commands 'merge-frames' and 'split-frame' * lisp/window-x.el (merge-frames, split-frame): New commands. * etc/NEWS: Announce new commands 'merge-frame' and 'split-frame'. diff --git a/etc/NEWS b/etc/NEWS index d10bc520388..18b2be9cb8c 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -454,6 +454,11 @@ either resize the frame and change the fullscreen status accordingly or keep the frame size unchanged. The value t means to first reset the fullscreen status and then resize the frame. +*** New commands 'split-frame' and 'merge-frames'. +'split-frame' moves a specified number of windows from an existing frame +to a newly-created frame. 'merge-frames' merges all windows from two +frames into one of these frames and deletes the other one. + --- *** Frames can now be renamed to "F" on text terminals. Unlike with other frame names, an attempt to rename to "F" throws diff --git a/lisp/window-x.el b/lisp/window-x.el index 37344e9101a..d35973e1465 100644 --- a/lisp/window-x.el +++ b/lisp/window-x.el @@ -315,5 +315,103 @@ ones in `window--transpose'." (if is-atom '(nil . t) conf) no-resize atom-windows))))) +;;;###autoload +(defun merge-frames (&optional frame1 frame2 vertical) + "Merge the main window of FRAME2 into FRAME1. +Split the main window of FRAME1 and make the new window display the main +window of FRAME2. Both FRAME1 and FRAME2 must be live frames. If +VERTICAL is non-nil, make the new window below the old main window of +FRAME1. Otherwise, make the new window on the right of FRAME1's main +window. Interactively, VERTICAL is the prefix argument, FRAME1 is the +selected frame and FRAME2 is the frame following FRAME1 in the frame +list. Delete FRAME2 if the merge completed successfully and return +FRAME1." + (interactive "i\ni\nP") + (let* ((frame1 (window-normalize-frame frame1)) + (frame2 (or (if frame2 + (window-normalize-frame frame2) + (next-frame frame1)) + (user-error "Cannot find frame to merge")))) + (window-state-put + ;; Source window on frame2. + (window-state-get (window-main-window frame2)) + ;; Make new window on frame1. + (split-window (window-main-window frame1) nil (not vertical))) + (delete-frame frame2) + frame1)) + +;;;###autoload +(defun split-frame (&optional frame arg) + "Split windows of specified FRAME into two separate frames. +FRAME must be a live frame and defaults to the selected frame. ARG +specifies the number of windows to consider for splitting and defaults +to 1. Interactively, ARG is the prefix argument. + +First divide the child windows of FRAME's main window into two parts. +The first part includes the first ARG child windows if ARG is positive, +or -ARG last windows if it's negative. The second part includes the +remaining child windows of FRAME's main window. Then clone into a +newly-created frame each of the windows of the part which does not +include FRAME's selected window and delete those windows from FRAME. + +Signal an error if ARG is either zero or not a number, if FRAME's main +window is live or does not have more child windows than specified by the +absolute value of ARG. Return the new frame." + (interactive "i\nP") + (let* ((frame (window-normalize-frame frame)) + ;; MAIN is FRAME'S main window. + (main (window-main-window frame)) + (total-window-count (window-child-count main)) + (arg (or arg 1))) + (cond + ((window-live-p main) + (user-error "Cannot split frame with only one window")) + ((or (not (numberp arg)) (zerop arg)) + (user-error "Invalid ARG %s for splitting frame" arg)) + ((>= (abs arg) total-window-count) + (user-error "ARG %s exceeds number of windows %s that can be split off" + (abs arg) (1- total-window-count))) + (t (let* ((reverse (< arg 0)) + ;; This is where the pivot window is. + (pivot-window-pos (- (if reverse + (+ total-window-count arg) + arg) + 1)) + (pivot-window (window-child main)) + (active-window (frame-selected-window frame)) + ;; If FRAME's selected window is on the left side of the + ;; pivot window. + (active-window-on-left (eq pivot-window active-window))) + ;; We want the 2nd level window that the active window is a + ;; part of. + (while (not (eq (window-parent active-window) main)) + (setq active-window (window-parent active-window))) + + ;; Now we need to find the pivot window + (dotimes (_ pivot-window-pos) + (setq pivot-window (window-next-sibling pivot-window)) + (when (eq active-window pivot-window) + (setq active-window-on-left t))) + + ;; Now we have pivot-window set, and we just need to + ;; combine. We want to split away all windows from the + ;; side of the pivot that doesn't contain the active + ;; window. + (let* ((first (window-child main)) + (last (window-last-child main)) + (next-pivot-sib (window-next-sibling pivot-window)) + (right-comb (if (eq next-pivot-sib last) + last + (combine-windows next-pivot-sib last))) + (left-comb (if (eq first pivot-window) + first + (combine-windows first pivot-window))) + ;; comb-win is the combination that will be + ;; split off. + (comb-win (if active-window-on-left right-comb left-comb))) + (window-state-put (window-state-get comb-win) + (window-main-window (make-frame))) + (delete-window comb-win))))))) + (provide 'window-x) ;;; window-x.el ends here