Merge remote-tracking branch 'savannah/master' into feature/tree-sitter

This commit is contained in:
Yuan Fu 2022-11-21 12:54:35 -08:00
commit aaeaa310f0
No known key found for this signature in database
GPG key ID: 56E19BC57664A442
692 changed files with 36720 additions and 8897 deletions

View file

@ -753,7 +753,7 @@ property for use by navigation."
(defun backtrace--line-length-or-nil ()
"Return `backtrace-line-length' if valid, nil else."
;; mirror the logic in `cl-print-to-string-with-limits'
;; mirror the logic in `cl-print-to-string-with-limit'
(and (natnump backtrace-line-length)
(not (zerop backtrace-line-length))
backtrace-line-length))

View file

@ -31,6 +31,7 @@
;;; Code:
(require 'cl-lib)
(eval-when-compile (require 'subr-x)) ;For `named-let'.
(defmacro benchmark-elapse (&rest forms)

View file

@ -163,7 +163,9 @@
(let ((s (substring bindat-raw bindat-idx (+ bindat-idx len))))
(setq bindat-idx (+ bindat-idx len))
(if (stringp s) s
(apply #'unibyte-string s))))
;; FIXME: There should be a more efficient way to do this.
;; Should `apply' accept vectors in addition to lists?
(apply #'unibyte-string (append s nil)))))
(defun bindat--unpack-strz (&optional len)
(let ((i 0) s)
@ -172,7 +174,7 @@
(setq s (substring bindat-raw bindat-idx (+ bindat-idx i)))
(setq bindat-idx (+ bindat-idx (or len (1+ i))))
(if (stringp s) s
(apply #'unibyte-string s))))
(apply #'unibyte-string (append s nil)))))
(defun bindat--unpack-bits (len)
(let ((bits nil) (bnum (1- (* 8 len))) j m)

View file

@ -178,7 +178,7 @@ Earlier variables shadow later ones with the same name.")
;; be displayed when the function's source file will be
;; compiled anyway, but more importantly we would otherwise
;; emit spurious warnings here because we don't have the full
;; context, such as `declare-functions' placed earlier in the
;; context, such as `declare-function's placed earlier in the
;; source file's code or `with-suppressed-warnings' that
;; surrounded the `defsubst'.
(byte-compile-warnings nil))

View file

@ -481,6 +481,11 @@ convention was modified."
(puthash (indirect-function function) signature
advertised-signature-table))
(defun get-advertised-calling-convention (function)
"Get the advertised SIGNATURE of FUNCTION.
Return t if there isn't any."
(gethash function advertised-signature-table t))
(defun make-obsolete (obsolete-name current-name when)
"Make the byte-compiler warn that function OBSOLETE-NAME is obsolete.
OBSOLETE-NAME should be a function name or macro name (a symbol).

View file

@ -129,6 +129,7 @@
;; us from emitting warnings when compiling files which use cl-lib without
;; requiring it! (bug#30635)
(eval-when-compile (require 'cl-lib))
(eval-when-compile (require 'subr-x))
;; The feature of compiling in a specific target Emacs version
;; has been turned off because compile time options are a bad idea.
@ -1185,27 +1186,22 @@ message buffer `default-directory'."
(defun byte-compile--first-symbol-with-pos (form)
"Return the first symbol with position in form, or nil if none.
Order is by depth-first search."
(cond
((symbol-with-pos-p form) form)
((consp form)
(or (byte-compile--first-symbol-with-pos (car form))
(let ((sym nil))
(setq form (cdr form))
(while (and (consp form)
(not (setq sym (byte-compile--first-symbol-with-pos
(car form)))))
(setq form (cdr form)))
(or sym
(and form (byte-compile--first-symbol-with-pos form))))))
((or (vectorp form) (recordp form))
(let ((len (length form))
(i 0)
(sym nil))
(while (and (< i len)
(not (setq sym (byte-compile--first-symbol-with-pos
(aref form i)))))
(setq i (1+ i)))
sym))))
(named-let loop ((form form)
(depth 10)) ;Arbitrary limit.
(cond
((<= depth 0) nil) ;Avoid cycles (bug#58601).
((symbol-with-pos-p form) form)
((consp form)
(or (loop (car form) (1- depth))
(loop (cdr form) (1- depth))))
((or (vectorp form) (recordp form))
(let ((len (length form))
(i 0)
(sym nil))
(while (and (< i len)
(not (setq sym (loop (aref form i) (1- depth)))))
(setq i (1+ i)))
sym)))))
(defun byte-compile--warning-source-offset ()
"Return a source offset from `byte-compile-form-stack' or nil if none."
@ -1405,11 +1401,11 @@ when printing the error message."
(and (not macro-p)
(compiled-function-p (symbol-function fn)))))
(setq fn (symbol-function fn)))
(let ((advertised (gethash (if (and (symbolp fn) (fboundp fn))
;; Could be a subr.
(symbol-function fn)
fn)
advertised-signature-table t)))
(let ((advertised (get-advertised-calling-convention
(if (and (symbolp fn) (fboundp fn))
;; Could be a subr.
(symbol-function fn)
fn))))
(cond
((listp advertised)
(if macro-p
@ -1469,9 +1465,11 @@ when printing the error message."
(defun byte-compile-arglist-signature-string (signature)
(cond ((null (cdr signature))
(format "%d+" (car signature)))
(format "%d or more" (car signature)))
((= (car signature) (cdr signature))
(format "%d" (car signature)))
((= (1+ (car signature)) (cdr signature))
(format "%d or %d" (car signature) (cdr signature)))
(t (format "%d-%d" (car signature) (cdr signature)))))
(defun byte-compile-function-warn (f nargs def)
@ -1884,6 +1882,9 @@ Files in subdirectories of DIRECTORY are processed also."
(interactive "DByte force recompile (directory): ")
(byte-recompile-directory directory nil t))
(defvar byte-compile-ignore-files nil
"List of regexps for files to ignore during byte compilation.")
;;;###autoload
(defun byte-recompile-directory (directory &optional arg force follow-symlinks)
"Recompile every `.el' file in DIRECTORY that needs recompilation.
@ -1940,14 +1941,22 @@ also be compiled."
;; This file is a subdirectory. Handle them differently.
(or (null arg) (eq 0 arg)
(y-or-n-p (concat "Check " source "? ")))
(setq directories (nconc directories (list source))))
;; Directory is requested to be ignored
(not (string-match-p
(regexp-opt byte-compile-ignore-files)
source))
(setq directories (nconc directories (list source))))
;; It is an ordinary file. Decide whether to compile it.
(if (and (string-match emacs-lisp-file-regexp source)
;; The next 2 tests avoid compiling lock files
(file-readable-p source)
(not (string-match "\\`\\.#" file))
(not (auto-save-file-name-p source))
(not (member source (dir-locals--all-files directory))))
(not (member source (dir-locals--all-files directory)))
;; File is requested to be ignored
(not (string-match-p
(regexp-opt byte-compile-ignore-files)
source)))
(progn (cl-incf
(pcase (byte-recompile-file source force arg)
('no-byte-compile skip-count)
@ -2321,9 +2330,15 @@ With argument ARG, insert value in current buffer after the form."
(setq case-fold-search nil))
(displaying-byte-compile-warnings
(with-current-buffer inbuffer
(and byte-compile-current-file
(byte-compile-insert-header byte-compile-current-file
byte-compile--outbuffer))
(when byte-compile-current-file
(byte-compile-insert-header byte-compile-current-file
byte-compile--outbuffer)
;; Instruct native-comp to ignore this file.
(when (bound-and-true-p no-native-compile)
(with-current-buffer byte-compile--outbuffer
(insert
"(when (boundp 'comp--no-native-compile)
(puthash load-file-name t comp--no-native-compile))\n\n"))))
(goto-char (point-min))
;; Should we always do this? When calling multiple files, it
;; would be useful to delay this warning until all have been
@ -2561,7 +2576,7 @@ list that represents a doc string reference.
;; macroexpand-all.
;; (if (memq byte-optimize '(t source))
;; (setq form (byte-optimize-form form for-effect)))
(cconv-closure-convert form))
(cconv-closure-convert form byte-compile-bound-variables))
;; byte-hunk-handlers cannot call this!
(defun byte-compile-toplevel-file-form (top-level-form)
@ -3622,7 +3637,7 @@ lambda-expression."
(byte-compile-out base-op tmp)))
(defun byte-compile-dynamic-variable-bind (var)
"Generate code to bind the lexical variable VAR to the top-of-stack value."
"Generate code to bind the dynamic variable VAR to the top-of-stack value."
(byte-compile-check-variable var 'let-bind)
(push var byte-compile-bound-variables)
(byte-compile-dynamic-variable-op 'byte-varbind var))
@ -4659,13 +4674,6 @@ Return the offset in the form (VAR . OFFSET)."
(byte-compile-form (cadr clause))
(byte-compile-push-constant nil)))))
(defun byte-compile-not-lexical-var-p (var)
(or (not (symbolp var))
(special-variable-p var)
(memq var byte-compile-bound-variables)
(memq var '(nil t))
(keywordp var)))
(defun byte-compile-bind (var init-lexenv)
"Emit byte-codes to bind VAR and update `byte-compile--lexical-environment'.
INIT-LEXENV should be a lexical-environment alist describing the
@ -4674,7 +4682,7 @@ Return non-nil if the TOS value was popped."
;; The mix of lexical and dynamic bindings mean that we may have to
;; juggle things on the stack, to move them to TOS for
;; dynamic binding.
(if (and lexical-binding (not (byte-compile-not-lexical-var-p var)))
(if (not (cconv--not-lexical-var-p var byte-compile-bound-variables))
;; VAR is a simple stack-allocated lexical variable.
(progn (push (assq var init-lexenv)
byte-compile--lexical-environment)

View file

@ -64,20 +64,12 @@
;;
;;; Code:
;; PROBLEM cases found during conversion to lexical binding.
;; We should try and detect and warn about those cases, even
;; for lexical-binding==nil to help prepare the migration.
;; - Uses of run-hooks, and friends.
;; - Cases where we want to apply the same code to different vars depending on
;; some test. These sometimes use a (let ((foo (if bar 'a 'b)))
;; ... (symbol-value foo) ... (set foo ...)).
;; TODO: (not just for cconv but also for the lexbind changes in general)
;; - let (e)debug find the value of lexical variables from the stack.
;; - make eval-region do the eval-sexp-add-defvars dance.
;; - byte-optimize-form should be applied before cconv.
;; OTOH, the warnings emitted by cconv-analyze need to come before optimize
;; since afterwards they can because obnoxious (warnings about an "unused
;; since afterwards they can become obnoxious (warnings about an "unused
;; variable" should not be emitted when the variable use has simply been
;; optimized away).
;; - let macros specify that some let-bindings come from the same source,
@ -87,33 +79,9 @@
;; - canonize code in macro-expand so we don't have to handle (let (var) body)
;; and other oddities.
;; - new byte codes for unwind-protect so that closures aren't needed at all.
;; - a reference to a var that is known statically to always hold a constant
;; should be turned into a byte-constant rather than a byte-stack-ref.
;; Hmm... right, that's called constant propagation and could be done here,
;; but when that constant is a function, we have to be careful to make sure
;; the bytecomp only compiles it once.
;; - Since we know here when a variable is not mutated, we could pass that
;; info to the byte-compiler, e.g. by using a new `immutable-let'.
;; - call known non-escaping functions with `goto' rather than `call'.
;; - optimize mapc to a dolist loop.
;; (defmacro dlet (binders &rest body)
;; ;; Works in both lexical and non-lexical mode.
;; (declare (indent 1) (debug let))
;; `(progn
;; ,@(mapcar (lambda (binder)
;; `(defvar ,(if (consp binder) (car binder) binder)))
;; binders)
;; (let ,binders ,@body)))
;; (defmacro llet (binders &rest body)
;; ;; Only works in lexical-binding mode.
;; `(funcall
;; (lambda ,(mapcar (lambda (binder) (if (consp binder) (car binder) binder))
;; binders)
;; ,@body)
;; ,@(mapcar (lambda (binder) (if (consp binder) (cadr binder)))
;; binders)))
(eval-when-compile (require 'cl-lib))
@ -142,13 +110,19 @@ is less than this number.")
;; interactive forms.
(make-hash-table :test #'eq :weakness 'key))
(defvar cconv--dynbound-variables nil
"List of variables known to be dynamically bound.")
;;;###autoload
(defun cconv-closure-convert (form)
(defun cconv-closure-convert (form &optional dynbound-vars)
"Main entry point for closure conversion.
FORM is a piece of Elisp code after macroexpansion.
DYNBOUND-VARS is a list of symbols that should be considered as
using dynamic scoping.
Returns a form where all lambdas don't have any free variables."
(let ((cconv-freevars-alist '())
(let ((cconv--dynbound-variables dynbound-vars)
(cconv-freevars-alist '())
(cconv-var-classification '()))
;; Analyze form - fill these variables with new information.
(cconv-analyze-form form '())
@ -156,8 +130,6 @@ Returns a form where all lambdas don't have any free variables."
(prog1 (cconv-convert form nil nil) ; Env initially empty.
(cl-assert (null cconv-freevars-alist)))))
(defconst cconv--dummy-var (make-symbol "ignored"))
(defun cconv--set-diff (s1 s2)
"Return elements of set S1 that are not in set S2."
(let ((res '()))
@ -262,9 +234,7 @@ Returns a form where all lambdas don't have any free variables."
;; it is often non-trivial for the programmer to avoid such
;; unused vars.
(not (intern-soft var))
(eq ?_ (aref (symbol-name var) 0))
;; As a special exception, ignore "ignored".
(eq var 'ignored))
(eq ?_ (aref (symbol-name var) 0)))
(let ((suggestions (help-uni-confusable-suggestions (symbol-name var))))
(format "Unused lexical %s `%S'%s"
varkind (bare-symbol var)
@ -342,7 +312,7 @@ EXTEND is a list of variables which might need to be accessed even from places
where they are shadowed, because some part of ENV causes them to be used at
places where they originally did not directly appear."
(cl-assert (not (delq nil (mapcar (lambda (mapping)
(if (eq (cadr mapping) 'apply-partially)
(if (eq (cadr mapping) #'apply-partially)
(cconv--set-diff (cdr (cddr mapping))
extend)))
env))))
@ -634,6 +604,12 @@ places where they originally did not directly appear."
(defvar byte-compile-lexical-variables)
(defun cconv--not-lexical-var-p (var dynbounds)
(or (not lexical-binding)
(not (symbolp var))
(special-variable-p var)
(memq var dynbounds)))
(defun cconv--analyze-use (vardata form varkind)
"Analyze the use of a variable.
VARDATA should be (BINDER READ MUTATED CAPTURED CALLED).
@ -677,7 +653,7 @@ FORM is the parent form that binds this var."
;; outside of it.
(envcopy
(mapcar (lambda (vdata) (list (car vdata) nil nil nil nil)) env))
(byte-compile-bound-variables byte-compile-bound-variables)
(cconv--dynbound-variables cconv--dynbound-variables)
(newenv envcopy))
;; Push it before recursing, so cconv-freevars-alist contains entries in
;; the order they'll be used by closure-convert-rec.
@ -685,7 +661,7 @@ FORM is the parent form that binds this var."
(when lexical-binding
(dolist (arg args)
(cond
((byte-compile-not-lexical-var-p arg)
((cconv--not-lexical-var-p arg cconv--dynbound-variables)
(byte-compile-warn-x
arg
"Lexical argument shadows the dynamic variable %S"
@ -715,6 +691,8 @@ FORM is the parent form that binds this var."
(setf (nth 3 (car env)) t))
(setq env (cdr env) envcopy (cdr envcopy))))))
(defvar cconv--dynbindings)
(defun cconv-analyze-form (form env)
"Find mutated variables and variables captured by closure.
Analyze lambdas if they are suitable for lambda lifting.
@ -730,7 +708,7 @@ This function does not return anything but instead fills the
(let ((orig-env env)
(newvars nil)
(var nil)
(byte-compile-bound-variables byte-compile-bound-variables)
(cconv--dynbound-variables cconv--dynbound-variables)
(value nil))
(dolist (binder binders)
(if (not (consp binder))
@ -743,7 +721,9 @@ This function does not return anything but instead fills the
(cconv-analyze-form value (if (eq letsym 'let*) env orig-env)))
(unless (or (byte-compile-not-lexical-var-p var) (not lexical-binding))
(if (cconv--not-lexical-var-p var cconv--dynbound-variables)
(when (boundp 'cconv--dynbindings)
(push var cconv--dynbindings))
(cl-pushnew var byte-compile-lexical-variables)
(let ((varstruct (list var nil nil nil nil)))
(push (cons binder (cdr varstruct)) newvars)
@ -797,7 +777,8 @@ This function does not return anything but instead fills the
(cconv-analyze-form protected-form env)
(unless lexical-binding
(setq var nil))
(when (and var (symbolp var) (byte-compile-not-lexical-var-p var))
(when (and var (symbolp var)
(cconv--not-lexical-var-p var cconv--dynbound-variables))
(byte-compile-warn-x
var "Lexical variable shadows the dynamic variable %S" var))
(let* ((varstruct (list var nil nil nil nil)))
@ -813,9 +794,9 @@ This function does not return anything but instead fills the
(cconv-analyze-form form env)
(cconv--analyze-function () body env form))
(`(defvar ,var) (push var byte-compile-bound-variables))
(`(defvar ,var) (push var cconv--dynbound-variables))
(`(,(or 'defconst 'defvar) ,var ,value . ,_)
(push var byte-compile-bound-variables)
(push var cconv--dynbound-variables)
(cconv-analyze-form value env))
(`(,(or 'funcall 'apply) ,fun . ,args)
@ -847,5 +828,78 @@ This function does not return anything but instead fills the
(setf (nth 1 dv) t))))))
(define-obsolete-function-alias 'cconv-analyse-form #'cconv-analyze-form "25.1")
(defun cconv-fv (form lexvars dynvars)
"Return the list of free variables in FORM.
LEXVARS is the list of statically scoped vars in the context
and DYNVARS is the list of dynamically scoped vars in the context.
Returns a pair (LEXV . DYNV) of those vars actually used by FORM."
(let* ((fun
;; Wrap FORM into a function because the analysis code we
;; have only computes freevars for functions.
;; In practice FORM is always already of the form
;; #'(lambda ...), so optimize for this case.
(if (and (eq 'function (car-safe form))
(eq 'lambda (car-safe (cadr form)))
;; To get correct results, FUN needs to be a "simple lambda"
;; without nested forms that aren't part of the body. :-(
(not (assq 'interactive (cadr form)))
(not (assq ':documentation (cadr form))))
form
`#'(lambda () ,form)))
(analysis-env (mapcar (lambda (v) (list v nil nil nil nil)) lexvars))
(cconv--dynbound-variables dynvars)
(byte-compile-lexical-variables nil)
(cconv--dynbindings nil)
(cconv-freevars-alist '())
(cconv-var-classification '()))
(let* ((body (cddr (cadr fun))))
;; Analyze form - fill these variables with new information.
(cconv-analyze-form fun analysis-env)
(setq cconv-freevars-alist (nreverse cconv-freevars-alist))
(unless (equal (if (eq :documentation (car-safe (car body)))
(cdr body) body)
(caar cconv-freevars-alist))
(message "BOOH!\n%S\n%S"
body (caar cconv-freevars-alist)))
(cl-assert (equal (if (eq :documentation (car-safe (car body)))
(cdr body) body)
(caar cconv-freevars-alist)))
(let ((fvs (nreverse (cdar cconv-freevars-alist)))
(dyns (delq nil (mapcar (lambda (var) (car (memq var dynvars)))
(delete-dups cconv--dynbindings)))))
(cons fvs dyns)))))
(defun cconv-make-interpreted-closure (fun env)
(cl-assert (eq (car-safe fun) 'lambda))
(let ((lexvars (delq nil (mapcar #'car-safe env))))
(if (null lexvars)
;; The lexical environment is empty, so there's no need to
;; look for free variables.
`(closure ,env . ,(cdr fun))
;; We could try and cache the result of the macroexpansion and
;; `cconv-fv' analysis. Not sure it's worth the trouble.
(let* ((form `#',fun)
(expanded-form
(let ((lexical-binding t) ;; Tell macros which dialect is in use.
;; Make the macro aware of any defvar declarations in scope.
(macroexp--dynvars
(if macroexp--dynvars
(append env macroexp--dynvars) env)))
(macroexpand-all form macroexpand-all-environment)))
;; Since we macroexpanded the body, we may as well use that.
(expanded-fun-cdr
(pcase expanded-form
(`#'(lambda . ,cdr) cdr)
(_ (cdr fun))))
(dynvars (delq nil (mapcar (lambda (b) (if (symbolp b) b)) env)))
(fvs (cconv-fv expanded-form lexvars dynvars))
(newenv (nconc (mapcar (lambda (fv) (assq fv env)) (car fvs))
(cdr fvs))))
;; Never return a nil env, since nil means to use the dynbind
;; dialect of ELisp.
`(closure ,(or newenv '(t)) . ,expanded-fun-cdr)))))
(provide 'cconv)
;;; cconv.el ends here

View file

@ -2265,8 +2265,8 @@ buffer, otherwise stop after the first error."
(unless (and sym (or (boundp sym) (fboundp sym)))
;; Find out how we spell-check this word.
(unless (or
;; All caps w/ option th, or s tacked on the end
;; for pluralization or number.
;; All caps with option th, or s tacked on the
;; end for pluralization or number.
(string-match "^[A-Z][A-Z]+\\(s\\|th\\)?$" word)
(looking-at "}") ; a keymap expression
)

View file

@ -615,12 +615,12 @@ PROPLIST is a list of the sort returned by `symbol-plist'.
,(funcall setter
`(cl--set-getf ,getter ,k ,val))
,val)))))))))
(let ((val-tail (cdr-safe (plist-member plist tag))))
(let ((val-tail (cdr (plist-member plist tag))))
(if val-tail (car val-tail) def)))
;;;###autoload
(defun cl--set-getf (plist tag val)
(let ((val-tail (cdr-safe (plist-member plist tag))))
(let ((val-tail (cdr (plist-member plist tag))))
(if val-tail (progn (setcar val-tail val) plist)
(cl-list* tag val plist))))

View file

@ -650,13 +650,17 @@ The set of acceptable TYPEs (also called \"specializers\") is defined
(cl--generic-name generic)
qualifiers specializers))
current-load-list :test #'equal)
(let (;; Prevent `defalias' from recording this as the definition site of
(let ((old-adv-cc (get-advertised-calling-convention
(symbol-function sym)))
;; Prevent `defalias' from recording this as the definition site of
;; the generic function.
current-load-list
;; BEWARE! Don't purify this function definition, since that leads
;; to memory corruption if the hash-tables it holds are modified
;; (the GC doesn't trace those pointers).
(purify-flag nil))
(when (listp old-adv-cc)
(set-advertised-calling-convention gfun old-adv-cc nil))
;; But do use `defalias', so that it interacts properly with nadvice,
;; e.g. for tracing/debug-on-entry.
(defalias sym gfun)))))

View file

@ -656,6 +656,8 @@ its argument list allows full Common Lisp conventions."
(check `(while ,var
(cond
((memq (car ,var) ',(append keys allow))
(unless (cdr ,var)
(error "Missing argument for %s" (car ,var)))
(setq ,var (cdr (cdr ,var))))
((car (cdr (memq (quote ,@allow) ,restarg)))
(setq ,var nil))

View file

@ -96,7 +96,7 @@ Integer values are handled in the `range' slot.")
`comp-common-supertype'.")
(subtype-p-mem (make-hash-table :test #'equal) :type hash-table
:documentation "Serve memoization for
`comp-subtype-p-mem'.")
`comp-cstr-ctxt-subtype-p-mem'.")
(union-1-mem-no-range (make-hash-table :test #'equal) :type hash-table
:documentation "Serve memoization for
`comp-cstr-union-1'.")

View file

@ -57,7 +57,7 @@
:safe #'integerp
:version "28.1")
(defcustom native-comp-debug (if (eq 'windows-nt system-type) 1 0)
(defcustom native-comp-debug 0
"Debug level for native compilation, a number between 0 and 3.
This is intended for debugging the compiler itself.
0 no debug output.
@ -67,7 +67,7 @@ This is intended for debugging the compiler itself.
passes and libgccjit log file."
:type 'natnum
:safe #'natnump
:version "28.1")
:version "29.1")
(defcustom native-comp-verbose 0
"Compiler verbosity for native compilation, a number between 0 and 3.
@ -687,6 +687,9 @@ Useful to hook into pass checkers.")
'native-compiler-error)
(defvar comp-no-spawn nil
"Non-nil don't spawn native compilation processes.")
;; Moved early to avoid circularity when comp.el is loaded and
;; `macroexpand' needs to be advised (bug#47049).
;;;###autoload
@ -696,12 +699,9 @@ Useful to hook into pass checkers.")
(memq subr-name native-comp-never-optimize-functions)
(gethash subr-name comp-installed-trampolines-h))
(cl-assert (subr-primitive-p (symbol-function subr-name)))
(comp--install-trampoline
subr-name
(or (comp-trampoline-search subr-name)
(comp-trampoline-compile subr-name)
;; Should never happen.
(cl-assert nil)))))
(when-let ((trampoline (or (comp-trampoline-search subr-name)
(comp-trampoline-compile subr-name))))
(comp--install-trampoline subr-name trampoline))))
(cl-defstruct (comp-vec (:copier nil))
@ -2057,9 +2057,10 @@ and the annotation emission."
"Lexically-scoped FUNCTION."
(let ((args (comp-func-l-args function)))
(cons (make-comp-mvar :constant (comp-args-base-min args))
(make-comp-mvar :constant (if (comp-args-p args)
(comp-args-max args)
'many)))))
(make-comp-mvar :constant (cond
((comp-args-p args) (comp-args-max args))
((comp-nargs-rest args) 'many)
(t (comp-nargs-nonrest args)))))))
(cl-defmethod comp-prepare-args-for-top-level ((function comp-func-d))
"Dynamically scoped FUNCTION."
@ -2822,7 +2823,7 @@ blocks."
(first-processed (l)
(if-let ((p (cl-find-if (lambda (p) (comp-block-idom p)) l)))
p
(signal 'native-ice "cant't find first preprocessed"))))
(signal 'native-ice "can't find first preprocessed"))))
(when-let ((blocks (comp-func-blocks comp-func))
(entry (gethash 'entry blocks))
@ -3715,7 +3716,8 @@ Prepare every function for final compilation and drive the C back-end."
(if (zerop
(call-process (expand-file-name invocation-name
invocation-directory)
nil t t "--batch" "-l" temp-file))
nil t t "-no-comp-spawn" "--batch" "-l"
temp-file))
(progn
(delete-file temp-file)
output)
@ -3927,6 +3929,7 @@ processes from `comp-async-compilations'"
"Start compiling files from `comp-files-queue' asynchronously.
When compilation is finished, run `native-comp-async-all-done-hook' and
display a message."
(cl-assert (null comp-no-spawn))
(if (or comp-files-queue
(> (comp-async-runnings) 0))
(unless (>= (comp-async-runnings) (comp-effective-async-max-jobs))
@ -3945,8 +3948,8 @@ display a message."
(file-newer-than-file-p
source-file (comp-el-to-eln-filename source-file))))
do (let* ((expr `((require 'comp)
(setq comp-async-compilation t)
(setq warning-fill-column most-positive-fixnum)
(setq comp-async-compilation t
warning-fill-column most-positive-fixnum)
,(let ((set (list 'setq)))
(dolist (var '(comp-file-preloaded-p
native-compile-target-directory
@ -4002,7 +4005,8 @@ display a message."
:command (list
(expand-file-name invocation-name
invocation-directory)
"--batch" "-l" temp-file)
"-no-comp-spawn" "--batch" "-l"
temp-file)
:sentinel
(lambda (process _event)
(run-hook-with-args
@ -4046,72 +4050,73 @@ the deferred compilation mechanism."
(stringp function-or-file))
(signal 'native-compiler-error
(list "Not a function symbol or file" function-or-file)))
(catch 'no-native-compile
(let* ((print-symbols-bare t)
(data function-or-file)
(comp-native-compiling t)
(byte-native-qualities nil)
(symbols-with-pos-enabled t)
;; Have byte compiler signal an error when compilation fails.
(byte-compile-debug t)
(comp-ctxt (make-comp-ctxt :output output
:with-late-load with-late-load)))
(comp-log "\n \n" 1)
(unwind-protect
(progn
(condition-case err
(cl-loop
with report = nil
for t0 = (current-time)
for pass in comp-passes
unless (memq pass comp-disabled-passes)
do
(comp-log (format "(%s) Running pass %s:\n"
function-or-file pass)
2)
(setf data (funcall pass data))
(push (cons pass (float-time (time-since t0))) report)
(cl-loop for f in (alist-get pass comp-post-pass-hooks)
do (funcall f data))
finally
(when comp-log-time-report
(comp-log (format "Done compiling %s" data) 0)
(cl-loop for (pass . time) in (reverse report)
do (comp-log (format "Pass %s took: %fs."
pass time) 0))))
(native-compiler-skip)
(t
(let ((err-val (cdr err)))
;; If we are doing an async native compilation print the
;; error in the correct format so is parsable and abort.
(if (and comp-async-compilation
(not (eq (car err) 'native-compiler-error)))
(progn
(message (if err-val
"%s: Error: %s %s"
"%s: Error %s")
function-or-file
(get (car err) 'error-message)
(car-safe err-val))
(kill-emacs -1))
;; Otherwise re-signal it adding the compilation input.
(signal (car err) (if (consp err-val)
(cons function-or-file err-val)
(list function-or-file err-val)))))))
(if (stringp function-or-file)
data
;; So we return the compiled function.
(native-elisp-load data)))
;; We may have created a temporary file when we're being
;; called with something other than a file as the argument.
;; Delete it.
(when (and (not (stringp function-or-file))
(not output)
comp-ctxt
(comp-ctxt-output comp-ctxt)
(file-exists-p (comp-ctxt-output comp-ctxt)))
(message "Deleting %s" (comp-ctxt-output comp-ctxt))
(delete-file (comp-ctxt-output comp-ctxt)))))))
(when (or (null comp-no-spawn) comp-async-compilation)
(catch 'no-native-compile
(let* ((print-symbols-bare t)
(data function-or-file)
(comp-native-compiling t)
(byte-native-qualities nil)
(symbols-with-pos-enabled t)
;; Have byte compiler signal an error when compilation fails.
(byte-compile-debug t)
(comp-ctxt (make-comp-ctxt :output output
:with-late-load with-late-load)))
(comp-log "\n \n" 1)
(unwind-protect
(progn
(condition-case err
(cl-loop
with report = nil
for t0 = (current-time)
for pass in comp-passes
unless (memq pass comp-disabled-passes)
do
(comp-log (format "(%s) Running pass %s:\n"
function-or-file pass)
2)
(setf data (funcall pass data))
(push (cons pass (float-time (time-since t0))) report)
(cl-loop for f in (alist-get pass comp-post-pass-hooks)
do (funcall f data))
finally
(when comp-log-time-report
(comp-log (format "Done compiling %s" data) 0)
(cl-loop for (pass . time) in (reverse report)
do (comp-log (format "Pass %s took: %fs."
pass time) 0))))
(native-compiler-skip)
(t
(let ((err-val (cdr err)))
;; If we are doing an async native compilation print the
;; error in the correct format so is parsable and abort.
(if (and comp-async-compilation
(not (eq (car err) 'native-compiler-error)))
(progn
(message (if err-val
"%s: Error: %s %s"
"%s: Error %s")
function-or-file
(get (car err) 'error-message)
(car-safe err-val))
(kill-emacs -1))
;; Otherwise re-signal it adding the compilation input.
(signal (car err) (if (consp err-val)
(cons function-or-file err-val)
(list function-or-file err-val)))))))
(if (stringp function-or-file)
data
;; So we return the compiled function.
(native-elisp-load data)))
;; We may have created a temporary file when we're being
;; called with something other than a file as the argument.
;; Delete it.
(when (and (not (stringp function-or-file))
(not output)
comp-ctxt
(comp-ctxt-output comp-ctxt)
(file-exists-p (comp-ctxt-output comp-ctxt)))
(message "Deleting %s" (comp-ctxt-output comp-ctxt))
(delete-file (comp-ctxt-output comp-ctxt))))))))
(defun native-compile-async-skip-p (file load selector)
"Return non-nil if FILE's compilation should be skipped.
@ -4119,6 +4124,7 @@ the deferred compilation mechanism."
LOAD and SELECTOR work as described in `native--compile-async'."
;; Make sure we are not already compiling `file' (bug#40838).
(or (gethash file comp-async-compilations)
(gethash (file-name-with-extension file "elc") comp--no-native-compile)
(cond
((null selector) nil)
((functionp selector) (not (funcall selector file)))
@ -4166,7 +4172,8 @@ bytecode definition was not changed in the meantime)."
(error "LOAD must be nil, t or 'late"))
(unless (listp files)
(setf files (list files)))
(let (file-list)
(let ((added-something nil)
file-list)
(dolist (file-or-dir files)
(cond ((file-directory-p file-or-dir)
(dolist (file (if recursively
@ -4194,16 +4201,31 @@ bytecode definition was not changed in the meantime)."
(make-directory out-dir t))
(if (file-writable-p out-filename)
(setf comp-files-queue
(append comp-files-queue `((,file . ,load))))
(append comp-files-queue `((,file . ,load)))
added-something t)
(display-warning 'comp
(format "No write access for %s skipping."
out-filename)))))))
(when (zerop (comp-async-runnings))
;; Perhaps nothing passed `native-compile-async-skip-p'?
(when (and added-something
;; Don't start if there's one already running.
(zerop (comp-async-runnings)))
(comp-run-async-workers))))
;;; Compiler entry points.
(defun comp-compile-all-trampolines ()
"Pre-compile AOT all trampolines."
(let ((comp-running-batch-compilation t)
;; We want to target only the 'native-lisp' directory.
(native-compile-target-directory
(car (last native-comp-eln-load-path))))
(mapatoms (lambda (f)
(when (subr-primitive-p (symbol-function f))
(message "Compiling trampoline for: %s" f)
(comp-trampoline-compile f))))))
;;;###autoload
(defun comp-lookup-eln (filename)
"Given a Lisp source FILENAME return the corresponding .eln file if found.
@ -4223,14 +4245,13 @@ Search happens in `native-comp-eln-load-path'."
(defun native-compile (function-or-file &optional output)
"Compile FUNCTION-OR-FILE into native code.
This is the synchronous entry-point for the Emacs Lisp native
compiler.
FUNCTION-OR-FILE is a function symbol, a form, or the filename of
an Emacs Lisp source file.
If OUTPUT is non-nil, use it as the filename for the compiled
object.
If FUNCTION-OR-FILE is a filename, return the filename of the
compiled object. If FUNCTION-OR-FILE is a function symbol or a
form, return the compiled function."
compiler. FUNCTION-OR-FILE is a function symbol, a form, or the
filename of an Emacs Lisp source file. If OUTPUT is non-nil, use
it as the filename for the compiled object. If FUNCTION-OR-FILE
is a filename, if the compilation was successful return the
filename of the compiled object. If FUNCTION-OR-FILE is a
function symbol or a form, if the compilation was successful
return the compiled function."
(comp--native-compile function-or-file nil output))
;;;###autoload
@ -4317,13 +4338,15 @@ of (commands) to run simultaneously."
;; `invocation-directory'.
(setq dir (expand-file-name dir invocation-directory))
(when (file-exists-p dir)
(dolist (subdir (directory-files dir t))
(dolist (subdir (seq-filter
(lambda (f) (not (string-match (rx "/." (? ".") eos) f)))
(directory-files dir t)))
(when (and (file-directory-p subdir)
(file-writable-p subdir)
(not (equal (file-name-nondirectory
(directory-file-name subdir))
comp-native-version-dir)))
(message "Deleting %s..." subdir)
(message "Deleting `%s'..." subdir)
;; We're being overly cautious here -- there shouldn't be
;; anything but .eln files in these directories.
(dolist (eln (directory-files subdir t "\\.eln\\(\\.tmp\\)?\\'"))
@ -4335,6 +4358,6 @@ of (commands) to run simultaneously."
(provide 'comp)
;; LocalWords: limplified limplified limplification limplify Limple LIMPLE libgccjit elc eln
;; LocalWords: limplified limplification limplify Limple LIMPLE libgccjit elc eln
;;; comp.el ends here

View file

@ -201,7 +201,7 @@ This function is modeled after `minibuffer-complete-and-exit'."
(if doexit (exit-minibuffer))))
(defun crm--choose-completion-string (choice buffer base-position
&rest ignored)
&rest _ignored)
"Completion string chooser for `completing-read-multiple'.
This is called from `choose-completion-string-functions'.
It replaces the string that is currently being completed, without

View file

@ -55,21 +55,24 @@
:group 'extensions)
(defcustom eldoc-idle-delay 0.50
"Number of seconds of idle time to wait before printing.
"Number of seconds of idle time to wait before displaying documentation.
If user input arrives before this interval of time has elapsed after the
last input, no documentation will be printed.
last input event, no documentation will be displayed.
If this variable is set to 0, no idle time is required."
If this variable is set to 0, display the documentation without any delay."
:type 'number)
(defcustom eldoc-print-after-edit nil
"If non-nil, eldoc info is only shown when editing.
"If non-nil, eldoc info is only shown after editing commands.
Changing the value requires toggling `eldoc-mode'."
:type 'boolean)
(defcustom eldoc-echo-area-display-truncation-message t
"If non-nil, provide verbose help when a message has been truncated.
If nil, truncated messages will just have \"...\" appended."
When this is non-nil, and the documentation string was truncated to
fit in the echo-area, the documentation will be followed by an
explanation of how to display the full documentation text.
If nil, truncated messages will just have \"...\" to indicate truncation."
:type 'boolean
:version "28.1")
@ -93,22 +96,24 @@ Note that this variable has no effect, unless
(defcustom eldoc-echo-area-use-multiline-p 'truncate-sym-name-if-fit
"Allow long ElDoc doc strings to resize echo area display.
If value is t, never attempt to truncate messages, even if the
echo area must be resized to fit.
If the value is t, never attempt to truncate messages, even if the
echo area must be resized to fit. In that case, Emacs will resize
the mini-window up to the limit set by `max-mini-window-height'.
If the value is a positive number, it is used to calculate a
number of logical lines of documentation that ElDoc is allowed to
put in the echo area. If a positive integer, the number is used
directly, while a float specifies the number of lines as a
proportion of the echo area frame's height.
number of screen lines of documentation that ElDoc is allowed to
put in the echo area. A positive integer specifies the maximum
number of lines directly, while a floating-point number specifies
the number of screen lines as a fraction of the echo area frame's
height.
If value is the symbol `truncate-sym-name-if-fit', the part of
If the value is the symbol `truncate-sym-name-if-fit', the part of
the doc string that represents a symbol's name may be truncated
if it will enable the rest of the doc string to fit on a single
line, without resizing the echo area.
If value is nil, a doc string is always truncated to fit in a
single line of display in the echo area.
If the value is nil, a doc string is always truncated to fit in a
single screen line of echo-area display.
Any resizing of the echo area additionally respects
`max-mini-window-height'."
@ -121,12 +126,12 @@ Any resizing of the echo area additionally respects
line" truncate-sym-name-if-fit)))
(defcustom eldoc-echo-area-prefer-doc-buffer nil
"Prefer ElDoc's documentation buffer if it is showing in some frame.
"Prefer ElDoc's documentation buffer if it is displayed in some window.
If this variable's value is t, ElDoc will skip showing
documentation in the echo area if the dedicated documentation
buffer (given by `eldoc-doc-buffer') is being displayed in some
window. If the value is the symbol `maybe', then the echo area
is only skipped if the documentation doesn't fit there."
buffer (displayed by `eldoc-doc-buffer') is already displayed in
some window. If the value is the symbol `maybe', then the echo area
is only skipped if the documentation needs to be truncated there."
:type 'boolean)
(defface eldoc-highlight-function-argument
@ -287,8 +292,10 @@ reflect the change."
(put 'eldoc-mode-line-string 'risky-local-variable t)
(defun eldoc-minibuffer-message (format-string &rest args)
"Display messages in the mode-line when in the minibuffer.
Otherwise work like `message'."
"Display message specified by FORMAT-STRING and ARGS on the mode-line as needed.
This function displays the message produced by formatting ARGS
with FORMAT-STRING on the mode line when the current buffer is a minibuffer.
Otherwise, it displays the message like `message' would."
(if (minibufferp)
(progn
(add-hook 'minibuffer-exit-hook
@ -632,8 +639,8 @@ If INTERACTIVE is t, also display the buffer."
(when interactive (eldoc-doc-buffer t)))
(defun eldoc-documentation-default ()
"Show first doc string for item at point.
Default value for `eldoc-documentation-strategy'."
"Show the first non-nil documentation string for item at point.
This is the default value for `eldoc-documentation-strategy'."
(run-hook-with-args-until-success 'eldoc-documentation-functions
(eldoc--make-callback :patient)))
@ -651,18 +658,18 @@ else wait for all doc strings."
t)
(defun eldoc-documentation-compose ()
"Show multiple doc strings at once after waiting for all.
Meant as a value for `eldoc-documentation-strategy'."
"Show multiple documentation strings together after waiting for all of them.
This is meant to be used as a value for `eldoc-documentation-strategy'."
(eldoc--documentation-compose-1 nil))
(defun eldoc-documentation-compose-eagerly ()
"Show multiple doc strings at once as soon as possible.
Meant as a value for `eldoc-documentation-strategy'."
"Show multiple documentation strings one by one as soon as possible.
This is meant to be used as a value for `eldoc-documentation-strategy'."
(eldoc--documentation-compose-1 t))
(defun eldoc-documentation-enthusiast ()
"Show most important doc string produced so far.
Meant as a value for `eldoc-documentation-strategy'."
"Show most important documentation string produced so far.
This is meant to be used as a value for `eldoc-documentation-strategy'."
(run-hook-wrapped 'eldoc-documentation-functions
(lambda (f)
(let* ((callback (eldoc--make-callback :enthusiast))
@ -692,40 +699,42 @@ Meant as a value for `eldoc-documentation-strategy'."
(eldoc--documentation-strategy-defcustom eldoc-documentation-strategy
eldoc-documentation-function
#'eldoc-documentation-default
"How to collect and organize results of `eldoc-documentation-functions'.
"How to collect and display results of `eldoc-documentation-functions'.
This variable controls how `eldoc-documentation-functions', which
specifies the sources of documentation, is queried and how its
results are organized before being displayed to the user. The
following values are allowed:
This variable controls how to call the functions in the special hook
`eldoc-documentation-functions', and how to organize their results
for display to the user. The functions in `eldoc-documentation-functions'
are the source of documentation, and act as back-end for ElDoc.
- `eldoc-documentation-default': calls functions in the special
hook in order until one is found that produces a doc string
value. Display only that value;
The following values are supported:
- `eldoc-documentation-compose': calls all functions in the
special hook and displays all of the resulting doc strings
together. Wait for all strings to be ready, and preserve their
relative order as specified by the order of functions in the hook;
- `eldoc-documentation-default': Call functions in the special
hook in order, until one of them returns a non-nil string
value. Display only that string.
- `eldoc-documentation-compose-eagerly': calls all functions in
the special hook and displays as many of the resulting doc
strings as possible, as soon as possible. Preserves the
relative order of doc strings;
- `eldoc-documentation-compose': Call all the functions in the
special hook and display all of the resulting strings together,
after all of the functions were called, and in the order of the
functions in the hook.
- `eldoc-documentation-enthusiast': calls all functions in the
special hook and displays only the most important resulting
docstring one at any given time. A function appearing first in
the special hook is considered more important.
- `eldoc-documentation-compose-eagerly': Call all the functions in
the special hook, and display each non-nil string as soon as it
is returned by a function, before calling the next function.
This variable can also be set to a function of no args that
returns something other than a string or nil and allows for some
- `eldoc-documentation-enthusiast': Call all the functions in the
special hook, and display only the most important resulting
string at any given time. A function appearing first in
the special hook is considered more important than those which
appear after it.
This variable can also be set to a function of no arguments that
returns something other than a string or nil, and allows for some
or all of the special hook `eldoc-documentation-functions' to be
run. In that case, the strategy function should follow that
other variable's protocol closely and endeavor to display the
resulting doc strings itself.
other variable's protocol closely and display the resulting doc
strings itself.
For backward compatibility to the \"old\" protocol, this variable
For backward compatibility with the \"old\" protocol, this variable
can also be set to a function that returns nil or a doc string,
depending whether or not there is documentation to display at
all."

View file

@ -560,6 +560,7 @@ The same keyword arguments are supported as in
'("mock"
(tramp-login-program "sh")
(tramp-login-args (("-i")))
(tramp-direct-async ("-c"))
(tramp-remote-shell "/bin/sh")
(tramp-remote-shell-args ("-c"))
(tramp-connection-timeout 10)))

View file

@ -208,7 +208,7 @@ is run. If a macro (possibly with side effects) is to be tested,
it has to be wrapped in `(eval (quote ...))'.
If NAME is already defined as a test and Emacs is running
in batch mode, an error is signalled.
in batch mode, an error is signaled.
\(fn NAME () [DOCSTRING] [:expected-result RESULT-TYPE] \
[:tags \\='(TAG...)] BODY...)"

View file

@ -445,16 +445,17 @@ The return value is the last VAL in the list.
,v))))))))))
(gv-define-expander plist-get
(lambda (do plist prop)
(lambda (do plist prop &optional predicate)
(macroexp-let2 macroexp-copyable-p key prop
(gv-letplace (getter setter) plist
(macroexp-let2 nil p `(cdr (plist-member ,getter ,key))
(macroexp-let2 nil p `(cdr (plist-member ,getter ,key ,predicate))
(funcall do
`(car ,p)
(lambda (val)
`(if ,p
(setcar ,p ,val)
,(funcall setter `(cons ,key (cons ,val ,getter)))))))))))
,(funcall setter
`(cons ,key (cons ,val ,getter)))))))))))
;;; Some occasionally handy extensions.

View file

@ -71,7 +71,8 @@
(:conc-name hierarchy--))
(roots (list)) ; list of the hierarchy roots (no parent)
(parents (make-hash-table :test 'equal)) ; map an item to its parent
(children (make-hash-table :test 'equal)) ; map an item to its childre
(children (make-hash-table :test 'equal)) ; map an item to its children
(delaying-parents (make-hash-table :test 'equal)) ; map an item to its childrenfn
;; cache containing the set of all items in the hierarchy
(seen-items (make-hash-table :test 'equal))) ; map an item to t
@ -133,7 +134,8 @@ keys are :key and :test."
"Create a hierarchy and return it."
(hierarchy--make))
(defun hierarchy-add-tree (hierarchy item parentfn &optional childrenfn acceptfn)
(defun hierarchy-add-tree (hierarchy item parentfn
&optional childrenfn acceptfn delay-children-p)
"In HIERARCHY, add ITEM.
PARENTFN is either nil or a function defining the child-to-parent
@ -151,33 +153,45 @@ CHILDRENFN are expected to be coherent with each other.
ACCEPTFN is a function returning non-nil if its parameter (any object)
should be an item of the hierarchy. By default, ACCEPTFN returns non-nil
if its parameter is non-nil."
if its parameter is non-nil.
DELAY-CHILDREN-P is a predicate determining whether the children that would
normally be processed by CHILDRENFN should, instead, have their processing be
delayed and stored to be processed by CHILDRENFN when the child is selected
during use of the hierarchy."
(unless (hierarchy-has-item hierarchy item)
(let ((acceptfn (or acceptfn #'identity)))
(hierarchy--seen-items-add hierarchy item)
(let ((parent (and parentfn (funcall parentfn item))))
(when (funcall acceptfn parent)
(hierarchy--add-relation hierarchy item parent acceptfn)
(hierarchy-add-tree hierarchy parent parentfn childrenfn)))
(let ((children (and childrenfn (funcall childrenfn item))))
(mapc (lambda (child)
(when (funcall acceptfn child)
(hierarchy--add-relation hierarchy child item acceptfn)
(hierarchy-add-tree hierarchy child parentfn childrenfn)))
children)))))
(hierarchy-add-tree hierarchy parent
parentfn (if delay-children-p nil childrenfn))))
(if (and childrenfn delay-children-p)
(map-put! (hierarchy--delaying-parents hierarchy) item childrenfn)
(let ((children (and childrenfn (funcall childrenfn item))))
(map-put! (hierarchy--delaying-parents hierarchy) item nil)
(mapc (lambda (child)
(when (funcall acceptfn child)
(hierarchy--add-relation hierarchy child item acceptfn)
(hierarchy-add-tree hierarchy child parentfn childrenfn)))
children))))))
(defun hierarchy-add-trees (hierarchy items parentfn &optional childrenfn acceptfn)
(defun hierarchy-add-trees (hierarchy items parentfn
&optional childrenfn acceptfn delay-children-p)
"Call `hierarchy-add-tree' on HIERARCHY and each element of ITEMS.
PARENTFN, CHILDRENFN and ACCEPTFN have the same meaning as in `hierarchy-add'."
PARENTFN, CHILDRENFN, ACCEPTFN, and DELAY-CHILDREN-P have the same meaning as in
`hierarchy-add'."
(seq-map (lambda (item)
(hierarchy-add-tree hierarchy item parentfn childrenfn acceptfn))
(hierarchy-add-tree hierarchy item parentfn
childrenfn acceptfn delay-children-p))
items))
(defun hierarchy-add-list (hierarchy list &optional wrap childrenfn)
"Add to HIERARCHY the sub-lists in LIST.
If WRAP is non-nil, allow duplicate items in LIST by wraping each
If WRAP is non-nil, allow duplicate items in LIST by wrapping each
item in a cons (id . item). The root's id is 1.
CHILDRENFN is a function (defaults to `cdr') taking LIST as a
@ -541,6 +555,30 @@ nil. The buffer is returned."
buffer))
(declare-function widget-convert "wid-edit")
(defun hierarchy--create-delayed-tree-widget (elem labelfn indent childrenfn)
"Return a list of tree-widgets for the children generated.
ELEM is the element of the hierarchy passed from
`hierarchy-convert-to-tree-widget'; it and the CHILDRENFN are used to generate
the children of the element dynamically.
LABELFN is the same function passed to `hierarchy-convert-to-tree-widget'.
INDENT is the same function passed to `hierarchy-convert-to-tree-widget'.
CHILDRENFN is the function used to discover the children of ELEM."
(lambda (_widget)
(mapcar
(lambda (item)
(widget-convert
'tree-widget
:tag (hierarchy-labelfn-to-string labelfn item indent)
:expander (hierarchy--create-delayed-tree-widget
item
labelfn
(1+ indent)
childrenfn)))
(funcall childrenfn elem))))
(defun hierarchy-convert-to-tree-widget (hierarchy labelfn)
"Return a tree-widget for HIERARCHY.
@ -550,10 +588,21 @@ node label."
(require 'wid-edit)
(require 'tree-widget)
(hierarchy-map-tree (lambda (item indent children)
(widget-convert
'tree-widget
:tag (hierarchy-labelfn-to-string labelfn item indent)
:args children))
(let ((childrenfn (map-elt
(hierarchy--delaying-parents hierarchy)
item)))
(apply
#'widget-convert
(list 'tree-widget
:tag (hierarchy-labelfn-to-string labelfn item indent)
(if childrenfn :expander :args)
(if childrenfn
(hierarchy--create-delayed-tree-widget
item
labelfn
(1+ indent)
childrenfn)
children)))))
hierarchy))
(defun hierarchy-tree-display (hierarchy labelfn &optional buffer)

View file

@ -196,18 +196,21 @@ present if the icon is represented by an image."
(image-supported-file-p file)
(propertize
" " 'display
(if-let ((height (plist-get keywords :height)))
(create-image file
nil nil
:height (if (eq height 'line)
(let ((props
(append
(if-let ((height (plist-get keywords :height)))
(list :height (if (eq height 'line)
(window-default-line-height)
height)
:scale 1
:rotation (or (plist-get keywords :rotation) 0)
:ascent (if (plist-member keywords :ascent)
(plist-get keywords :ascent)
'center))
(create-image file))))))
height)))
'(:scale 1)
(if-let ((rotation (plist-get keywords :rotation)))
(list :rotation rotation))
(if-let ((margin (plist-get keywords :margin)))
(list :margin margin))
(list :ascent (if (plist-member keywords :ascent)
(plist-get keywords :ascent)
'center)))))
(apply 'create-image file nil nil props))))))
(cl-defmethod icons--create ((_type (eql 'emoji)) icon _keywords)
(when-let ((font (and (display-multi-font-p)

View file

@ -283,6 +283,12 @@ expression, in which case we want to handle forms differently."
,@(when-let ((safe (plist-get props :safe)))
`((put ',varname 'safe-local-variable ,safe))))))
;; Extract theme properties.
((eq car 'deftheme)
(let* ((name (car-safe (cdr-safe form)))
(props (nthcdr 3 form)))
`(put ',name 'theme-properties (list ,@props))))
((eq car 'defgroup)
;; In Emacs this is normally handled separately by cus-dep.el, but for
;; third party packages, it can be convenient to explicitly autoload
@ -730,7 +736,14 @@ rules for built-in packages and excluded files."
;; updated.
(file-newer-than-file-p
(expand-file-name "emacs-lisp/loaddefs-gen.el" lisp-directory)
output-file))))
output-file)))
(let ((lisp-mode-autoload-regexp
"^;;;###\\(\\(noexist\\)-\\)?\\(theme-autoload\\)"))
(loaddefs-generate
(expand-file-name "../etc/themes/" lisp-directory)
(expand-file-name "theme-loaddefs.el" lisp-directory))))
;;;###autoload (load "theme-loaddefs.el" t)
(provide 'loaddefs-gen)

View file

@ -5,7 +5,7 @@
;; Author: Nicolas Petton <nicolas@petton.fr>
;; Maintainer: emacs-devel@gnu.org
;; Keywords: extensions, lisp
;; Version: 3.2.1
;; Version: 3.3.1
;; Package-Requires: ((emacs "26"))
;; This file is part of GNU Emacs.
@ -80,48 +80,82 @@ MAP can be an alist, plist, hash-table, or array."
`(pcase-let ((,(map--make-pcase-patterns keys) ,map))
,@body))
(eval-when-compile
(defmacro map--dispatch (map-var &rest args)
"Evaluate one of the forms specified by ARGS based on the type of MAP-VAR.
The following keyword types are meaningful: `:list',
`:hash-table' and `:array'.
An error is thrown if MAP-VAR is neither a list, hash-table nor array.
Returns the result of evaluating the form associated with MAP-VAR's type."
(declare (debug t) (indent 1))
`(cond ((listp ,map-var) ,(plist-get args :list))
((hash-table-p ,map-var) ,(plist-get args :hash-table))
((arrayp ,map-var) ,(plist-get args :array))
(t (error "Unsupported map type `%S': %S"
(type-of ,map-var) ,map-var)))))
(define-error 'map-not-inplace "Cannot modify map in-place")
(defsubst map--plist-p (list)
"Return non-nil if LIST is the start of a nonempty plist map."
(and (consp list) (atom (car list))))
(defconst map--plist-has-predicate
(condition-case nil
(with-no-warnings (plist-get () nil #'eq) t)
(wrong-number-of-arguments))
"Non-nil means `plist-get' & co. accept a predicate in Emacs 29+.
Note that support for this predicate in map.el is patchy and
deprecated.")
(defun map--plist-member-1 (plist prop &optional predicate)
"Compatibility shim for the PREDICATE argument of `plist-member'.
Assumes non-nil PLIST satisfies `map--plist-p'."
(if (or (memq predicate '(nil eq)) (null plist))
(plist-member plist prop)
(let ((tail plist) found)
(while (and (not (setq found (funcall predicate (car tail) prop)))
(consp (setq tail (cdr tail)))
(consp (setq tail (cdr tail)))))
(and tail (not found)
(signal 'wrong-type-argument `(plistp ,plist)))
tail)))
(defalias 'map--plist-member
(if map--plist-has-predicate #'plist-member #'map--plist-member-1)
"Compatibility shim for `plist-member' in Emacs 29+.
\n(fn PLIST PROP &optional PREDICATE)")
(defun map--plist-put-1 (plist prop val &optional predicate)
"Compatibility shim for the PREDICATE argument of `plist-put'.
Assumes non-nil PLIST satisfies `map--plist-p'."
(if (or (memq predicate '(nil eq)) (null plist))
(plist-put plist prop val)
(let ((tail plist) prev found)
(while (and (consp (cdr tail))
(not (setq found (funcall predicate (car tail) prop)))
(consp (setq prev tail tail (cddr tail)))))
(cond (found (setcar (cdr tail) val))
(tail (signal 'wrong-type-argument `(plistp ,plist)))
(prev (setcdr (cdr prev) (cons prop (cons val (cddr prev)))))
((setq plist (cons prop (cons val plist)))))
plist)))
(defalias 'map--plist-put
(if map--plist-has-predicate #'plist-put #'map--plist-put-1)
"Compatibility shim for `plist-put' in Emacs 29+.
\n(fn PLIST PROP VAL &optional PREDICATE)")
(cl-defgeneric map-elt (map key &optional default testfn)
"Look up KEY in MAP and return its associated value.
If KEY is not found, return DEFAULT which defaults to nil.
TESTFN is the function to use for comparing keys. It is
deprecated because its default and valid values depend on the MAP
argument. Generally, alist keys are compared with `equal', plist
keys with `eq', and hash-table keys with the hash-table's test
argument, and it was never consistently supported by the map.el
API. Generally, alist keys are compared with `equal', plist keys
with `eq', and hash-table keys with the hash-table's test
function.
In the base definition, MAP can be an alist, plist, hash-table,
or array."
(declare
;; `testfn' is deprecated.
(advertised-calling-convention (map key &optional default) "27.1")
(gv-expander
(lambda (do)
(gv-letplace (mgetter msetter) `(gv-delay-error ,map)
(macroexp-let2* nil
;; Eval them once and for all in the right order.
((key key) (default default) (testfn testfn))
(funcall do `(map-elt ,mgetter ,key ,default)
(funcall do
`(map-elt ,mgetter ,key ,default ,@(and testfn `(,testfn)))
(lambda (v)
(macroexp-let2 nil v v
`(condition-case nil
@ -132,19 +166,21 @@ or array."
,(funcall msetter
`(map-insert ,mgetter ,key ,v))
;; Always return the value.
,v)))))))))
;; `testfn' is deprecated.
(advertised-calling-convention (map key &optional default) "27.1"))
;; Can't use `cl-defmethod' with `advertised-calling-convention'.
(map--dispatch map
:list (if (map--plist-p map)
(let ((res (plist-member map key)))
(if res (cadr res) default))
(alist-get key map default nil (or testfn #'equal)))
:hash-table (gethash key map default)
:array (if (map-contains-key map key)
(aref map key)
default)))
,v)))))))))))
(cl-defmethod map-elt ((map list) key &optional default testfn)
(if (map--plist-p map)
(let ((res (map--plist-member map key testfn)))
(if res (cadr res) default))
(alist-get key map default nil (or testfn #'equal))))
(cl-defmethod map-elt ((map hash-table) key &optional default _testfn)
(gethash key map default))
(cl-defmethod map-elt ((map array) key &optional default _testfn)
(if (map-contains-key map key)
(aref map key)
default))
(defmacro map-put (map key value &optional testfn)
"Associate KEY with VALUE in MAP and return VALUE.
@ -154,8 +190,12 @@ When MAP is an alist, test equality with TESTFN if non-nil,
otherwise use `equal'.
MAP can be an alist, plist, hash-table, or array."
(declare (obsolete "use map-put! or (setf (map-elt ...) ...) instead" "27.1"))
`(setf (map-elt ,map ,key nil ,testfn) ,value))
(declare
(obsolete "use `map-put!' or `(setf (map-elt ...) ...)' instead." "27.1"))
(if testfn
`(with-no-warnings
(setf (map-elt ,map ,key nil ,testfn) ,value))
`(setf (map-elt ,map ,key) ,value)))
(defun map--plist-delete (map key)
(let ((tail map) last)
@ -338,15 +378,16 @@ The default implementation delegates to `map-length'."
"Return non-nil if and only if MAP contains KEY.
TESTFN is deprecated. Its default depends on MAP.
The default implementation delegates to `map-some'."
(declare (advertised-calling-convention (map key) "27.1"))
(unless testfn (setq testfn #'equal))
(map-some (lambda (k _v) (funcall testfn key k)) map))
(cl-defmethod map-contains-key ((map list) key &optional testfn)
"Return non-nil if MAP contains KEY.
If MAP is an alist, TESTFN defaults to `equal'.
If MAP is a plist, `plist-member' is used instead."
If MAP is a plist, TESTFN defaults to `eq'."
(if (map--plist-p map)
(plist-member map key)
(map--plist-member map key testfn)
(let ((v '(nil)))
(not (eq v (alist-get key map v nil (or testfn #'equal)))))))
@ -459,24 +500,30 @@ This operates by modifying MAP in place.
If it cannot do that, it signals a `map-not-inplace' error.
To insert an element without modifying MAP, use `map-insert'."
;; `testfn' only exists for backward compatibility with `map-put'!
(declare (advertised-calling-convention (map key value) "27.1"))
;; Can't use `cl-defmethod' with `advertised-calling-convention'.
(map--dispatch
map
:list
(progn
(if (map--plist-p map)
(plist-put map key value)
(let ((oldmap map))
(setf (alist-get key map key nil (or testfn #'equal)) value)
(unless (eq oldmap map)
(signal 'map-not-inplace (list oldmap)))))
;; Always return the value.
value)
:hash-table (puthash key value map)
;; FIXME: If `key' is too large, should we signal `map-not-inplace'
;; and let `map-insert' grow the array?
:array (aset map key value)))
(declare (advertised-calling-convention (map key value) "27.1")))
(cl-defmethod map-put! ((map list) key value &optional testfn)
(if (map--plist-p map)
(map--plist-put map key value testfn)
(let ((oldmap map))
(setf (alist-get key map key nil (or testfn #'equal)) value)
(unless (eq oldmap map)
(signal 'map-not-inplace (list oldmap)))))
;; Always return the value.
value)
(cl-defmethod map-put! ((map hash-table) key value &optional _testfn)
(puthash key value map))
(cl-defmethod map-put! ((map array) key value &optional _testfn)
;; FIXME: If `key' is too large, should we signal `map-not-inplace'
;; and let `map-insert' grow the array?
(aset map key value))
;; There shouldn't be old source code referring to `map--put', yet we do
;; need to keep it for backward compatibility with .elc files where the
;; expansion of `setf' may call this function.
(define-obsolete-function-alias 'map--put #'map-put! "27.1")
(cl-defgeneric map-insert (map key value)
"Return a new map like MAP except that it associates KEY with VALUE.
@ -493,11 +540,6 @@ The default implementation defaults to `map-copy' and `map-put!'."
(cons key (cons value map))
(cons (cons key value) map)))
;; There shouldn't be old source code referring to `map--put', yet we do
;; need to keep it for backward compatibility with .elc files where the
;; expansion of `setf' may call this function.
(define-obsolete-function-alias 'map--put #'map-put! "27.1")
(cl-defmethod map-apply (function (map list))
(if (map--plist-p map)
(cl-call-next-method)

View file

@ -262,12 +262,7 @@ by counted more than once."
(cl-struct-slot-info struct-type)))))
(defun memory-report--format (bytes)
(setq bytes (/ bytes 1024.0))
(let ((units '("KiB" "MiB" "GiB" "TiB")))
(while (>= bytes 1024)
(setq bytes (/ bytes 1024.0))
(setq units (cdr units)))
(format "%6.1f %s" bytes (car units))))
(format "%10s" (file-size-human-readable bytes 'iec " ")))
(defun memory-report--gc-elem (elems type)
(* (nth 1 (assq type elems))

View file

@ -19,7 +19,18 @@
;;; Commentary:
;; This library provides multisession variables for Emacs Lisp, to
;; make them persist between sessions.
;;
;; Use `define-multisession-variable' to define a multisession
;; variable, and `multisession-value' to read its value. Use
;; `list-multisession-values' to list multisession variables.
;;
;; Users might want to customize `multisession-storage' and
;; `multisession-directory'.
;;
;; See Info node `(elisp) Multisession Variables' for more
;; information.
;;; Code:

View file

@ -216,7 +216,7 @@ is a list of additional properties among the following:
function) named COPIER. It will take an object of type NAME as first
argument followed by ARGS. ARGS lists the names of the slots that will
be updated with the value of the corresponding argument.
SLOTS is a list if slot descriptions. Each slot can be a single symbol
SLOTS is a list of slot descriptions. Each slot can be a single symbol
which is the name of the slot, or it can be of the form (SLOT-NAME . SPROPS)
where SLOT-NAME is then the name of the slot and SPROPS is a property
list of slot properties. The currently known properties are the following:
@ -341,11 +341,11 @@ list of slot properties. The currently known properties are the following:
(defmacro oclosure--lambda (type bindings mutables args &rest body)
"Low level construction of an OClosure object.
TYPE should be a form returning an OClosure type (a symbol)
TYPE should be a form returning an OClosure type (a symbol).
BINDINGS should list all the slots expected by this type, in the proper order.
MUTABLE is a list of symbols indicating which of the BINDINGS
should be mutable.
No checking is performed,"
No checking is performed."
(declare (indent 3) (debug (sexp (&rest (sexp form)) sexp def-body)))
;; FIXME: Fundamentally `oclosure-lambda' should be a special form.
;; We define it here as a macro which expands to something that

View file

@ -0,0 +1,791 @@
;;; package-vc.el --- Manage packages from VC checkouts -*- lexical-binding: t; -*-
;; Copyright (C) 2022 Free Software Foundation, Inc.
;; Author: Philip Kaludercic <philipk@posteo.net>
;; Keywords: tools
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;; While packages managed by package.el use tarballs for distributing
;; the source code, this extension allows for packages to be fetched
;; and updated directly from a version control system.
;;
;; To install a package from source use `package-vc-install'. If you
;; aren't interested in activating a package, you can use
;; `package-vc-checkout' instead, which will prompt you for a target
;; directory. If you wish to re-use an existing checkout, the command
;; `package-vc-install-from-checkout' will create a symbolic link and
;; prepare the package.
;;
;; If you make local changes that you wish to share with an upstream
;; maintainer, the command `package-vc-prepare-patch' can prepare
;; these as patches to send via Email.
;;; TODO:
;; - Allow maintaining patches that are ported back onto regular
;; packages and maintained between versions.
;;
;; - Add a heuristic for guessing a `:lisp-dir' when cloning directly
;; from a URL.
;;; Code:
(eval-when-compile (require 'rx))
(eval-when-compile (require 'inline))
(eval-when-compile (require 'map))
(require 'package)
(require 'lisp-mnt)
(require 'vc)
(require 'seq)
(defgroup package-vc nil
"Manage packages from VC checkouts."
:group 'package
:link '(custom-manual "(emacs) Package from Source")
:prefix "package-vc-"
:version "29.1")
(defconst package-vc--elpa-packages-version 1
"Version number of the package specification format understood by package-vc.")
(defcustom package-vc-heuristic-alist
`((,(rx bos "http" (? "s") "://"
(or (: (? "www.") "github.com"
"/" (+ (or alnum "-" "." "_"))
"/" (+ (or alnum "-" "." "_")))
(: "codeberg.org"
"/" (+ (or alnum "-" "." "_"))
"/" (+ (or alnum "-" "." "_")))
(: (? "www.") "gitlab" (+ "." (+ alnum))
"/" (+ (or alnum "-" "." "_"))
"/" (+ (or alnum "-" "." "_")))
(: "git.sr.ht"
"/~" (+ (or alnum "-" "." "_"))
"/" (+ (or alnum "-" "." "_")))
(: "git." (or "savannah" "sv") "." (? "non") "gnu.org/"
(or "r" "git") "/"
(+ (or alnum "-" "." "_")) (? "/")))
(or (? "/") ".git") eos)
. Git)
(,(rx bos "http" (? "s") "://"
(or (: "hg.sr.ht"
"/~" (+ (or alnum "-" "." "_"))
"/" (+ (or alnum "-" "." "_")))
(: "hg." (or "savannah" "sv") "." (? "non") "gnu.org/hgweb/"
(+ (or alnum "-" "." "_")) (? "/")))
eos)
. Hg)
(,(rx bos "http" (? "s") "://"
(or (: "bzr." (or "savannah" "sv") "." (? "non") "gnu.org/r/"
(+ (or alnum "-" "." "_")) (? "/")))
eos)
. Bzr))
"Heuristic mapping URL regular expressions to VC backends."
:type `(alist :key-type (regexp :tag "Regular expression matching URLs")
:value-type (choice :tag "VC Backend"
,@(mapcar (lambda (b) `(const ,b))
vc-handled-backends)))
:version "29.1")
(defcustom package-vc-default-backend 'Git
"Default VC backend used when cloning a package repository.
If no repository type was specified or could be guessed by
`package-vc-heuristic-alist', this is the default VC backend
used as fallback. The value must be a member of
`vc-handled-backends' and the named backend must implement
the `clone' function."
:type `(choice ,@(mapcar (lambda (b) (list 'const b))
vc-handled-backends))
:version "29.1")
(defvar package-vc-selected-packages) ; pacify byte-compiler
;;;###autoload
(defun package-vc-install-selected-packages ()
"Ensure packages specified in `package-vc-selected-packages' are installed."
(interactive)
(pcase-dolist (`(,name . ,spec) package-vc-selected-packages)
(when (stringp name)
(setq name (intern name)))
(let ((pkg-descs (assoc name package-alist #'string=)))
(unless (seq-some #'package-vc-p (cdr pkg-descs))
(cond
((null spec)
(package-vc-install name))
((stringp spec)
(package-vc-install name nil spec))
((listp spec)
(package-vc--archives-initialize)
(package-vc--unpack (cadr pkg-descs) spec)))))))
;;;###autoload
(defcustom package-vc-selected-packages '()
"List of packages that must be installed.
Each member of the list is of the form (NAME . SPEC), where NAME
is a symbol designating the package and SPEC is one of:
- nil, if any package version can be installed;
- a version string, if that specific revision is to be installed;
- a property list, describing a package specification. Valid
key/value pairs are
`:url' (string)
The URL of the repository used to fetch the package source.
`:branch' (string)
If given, the name of the branch to checkout after cloning the directory.
`:lisp-dir' (string)
The repository-relative name of the directory to use for loading the Lisp
sources. If not given, the value defaults to the root directory
of the repository.
`:main-file' (string)
The main file of the project, relevant to gather package metadata.
If not given, the assumed default is the package name with \".el\"
appended to it.
`:vc-backend' (symbol)
A symbol of the VC backend to use for cloning the package. The
value ought to be a member of `vc-handled-backends'. If omitted,
`vc-clone' will fall back onto the archive default or on
`package-vc-default-backend'.
All other keys are ignored.
This user option differs from `package-selected-packages' in that
it is meant to be specified manually. If you want to install all
the packages in the list, you cal also use
`package-vc-install-selected-packages'.
Note that this option will not override an existing source
package installation or revert the checked out revision."
:type '(alist :tag "List of packages you want to be installed"
:key-type (symbol :tag "Package")
:value-type
(choice (const :tag "Any revision" nil)
(string :tag "Specific revision")
(plist :options ((:url string)
(:branch string)
(:lisp-dir string)
(:main-file string)
(:vc-backend symbol)))))
:initialize #'custom-initialize-default
:set (lambda (sym val)
(custom-set-default sym val)
(package-vc-install-selected-packages))
:version "29.1")
(defvar package-vc--archive-spec-alist nil
"List of package specifications for each archive.
The list maps each package name, as a string, to a plist as
specified in `package-vc-selected-packages'.")
(defvar package-vc--archive-data-alist nil
"List of package specification metadata for archives.
Each element of the list has the form (ARCHIVE . PLIST), where
PLIST keys are one of:
`:version' (integer)
Indicates the version of the file formatting, to be compared
with `package-vc--elpa-packages-version'.
`:vc-backend' (symbol)
A symbol of the default VC backend to use if a package specification
does not indicate a backend. The value ought to be a member of
`vc-handled-backends'. If omitted, `vc-clone' will fall back on
`package-vc-default-backend'.
All other values are ignored.")
(defun package-vc--desc->spec (pkg-desc &optional name)
"Retrieve the package specification for PKG-DESC.
The optional argument NAME can be used to override the default
name for PKG-DESC."
(alist-get
(or name (package-desc-name pkg-desc))
(if (package-desc-archive pkg-desc)
(alist-get (intern (package-desc-archive pkg-desc))
package-vc--archive-spec-alist)
(apply #'append (mapcar #'cdr package-vc--archive-spec-alist)))
nil nil #'string=))
(define-inline package-vc--query-spec (pkg-desc prop)
"Query the property PROP for the package specification of PKG-DESC.
If no package specification can be determined, the function will
return nil."
(inline-letevals (pkg-desc prop)
(inline-quote (plist-get (package-vc--desc->spec ,pkg-desc) ,prop))))
(defun package-vc--read-archive-data (archive)
"Update `package-vc--archive-spec-alist' for ARCHIVE.
This function is meant to be used as a hook for `package-read-archive-hook'."
(let ((contents-file (expand-file-name
(format "archives/%s/elpa-packages.eld" archive)
package-user-dir)))
(when (file-exists-p contents-file)
(with-temp-buffer
(let ((coding-system-for-read 'utf-8))
(insert-file-contents contents-file)
;; The response from the server is expected to have the form
;;
;; ((("foo" :url "..." ...) ...)
;; :version 1
;; :default-vc Git)
(let ((spec (read (current-buffer))))
(when (eq package-vc--elpa-packages-version
(plist-get (cdr spec) :version))
(setf (alist-get (intern archive) package-vc--archive-spec-alist)
(car spec)))
(setf (alist-get (intern archive) package-vc--archive-data-alist)
(cdr spec))
(when-let ((default-vc (plist-get (cdr spec) :default-vc))
((not (memq default-vc vc-handled-backends))))
(warn "Archive `%S' expects missing VC backend %S"
archive (plist-get (cdr spec) :default-vc)))))))))
(defun package-vc--download-and-read-archives (&optional async)
"Download specifications of all `package-archives' and read them.
Populate `package-vc--archive-spec-alist' with the result.
If optional argument ASYNC is non-nil, perform the downloads
asynchronously."
(dolist (archive package-archives)
(condition-case-unless-debug nil
(package--download-one-archive archive "elpa-packages.eld" async)
(error (message "Failed to download `%s' archive." (car archive))))))
(add-hook 'package-read-archive-hook #'package-vc--read-archive-data 20)
(defun package-vc-commit (pkg)
"Return the last commit of a development package PKG."
(cl-assert (package-vc-p pkg))
;; FIXME: vc should be extended to allow querying the commit of a
;; directory (as is possible when dealing with git repositories).
;; This should be a fallback option.
(cl-loop with dir = (package-desc-dir pkg)
for file in (directory-files dir t "\\.el\\'" t)
when (vc-working-revision file) return it
finally return "unknown"))
(defun package-vc--version (pkg)
"Return the version number for the source package PKG."
(cl-assert (package-vc-p pkg))
(if-let ((main-file (package-vc--main-file pkg)))
(with-temp-buffer
(insert-file-contents main-file)
(package-strip-rcs-id
(or (lm-header "package-version")
(lm-header "version"))))
"0"))
(defun package-vc--main-file (pkg-desc)
"Return the name of the main file for PKG-DESC."
(cl-assert (package-vc-p pkg-desc))
(let ((pkg-spec (package-vc--desc->spec pkg-desc))
(name (symbol-name (package-desc-name pkg-desc))))
(or (plist-get pkg-spec :main-file)
(expand-file-name
(concat name ".el")
(file-name-concat
(or (package-desc-dir pkg-desc)
(expand-file-name name package-user-dir))
(plist-get pkg-spec :lisp-dir))))))
(defun package-vc--generate-description-file (pkg-desc pkg-file)
"Generate a package description file for PKG-DESC and write it to PKG-FILE."
(let ((name (package-desc-name pkg-desc)))
;; Infer the subject if missing.
(unless (package-desc-summary pkg-desc)
(setf (package-desc-summary pkg-desc)
(let ((main-file (package-vc--main-file pkg-desc)))
(or (package-desc-summary pkg-desc)
(and-let* ((pkg (cadr (assq name package-archive-contents))))
(package-desc-summary pkg))
(and main-file (file-exists-p main-file)
(lm-summary main-file))
package--default-summary))))
(let ((print-level nil)
(print-quoted t)
(print-length nil))
(write-region
(concat
";;; Generated package description from "
(replace-regexp-in-string
"-pkg\\.el\\'" ".el"
(file-name-nondirectory pkg-file))
" -*- no-byte-compile: t -*-\n"
(prin1-to-string
(nconc
(list 'define-package
(symbol-name name)
(cons 'vc (package-vc--version pkg-desc))
(package-desc-summary pkg-desc)
(let ((requires (package-desc-reqs pkg-desc)))
(list 'quote
;; Turn version lists into string form.
(mapcar
(lambda (elt)
(list (car elt)
(package-version-join (cadr elt))))
requires))))
(package--alist-to-plist-args
(package-desc-extras pkg-desc))))
"\n")
nil pkg-file nil 'silent))))
(declare-function org-export-to-file "ox" (backend file))
(defun package-vc--build-documentation (pkg-desc file)
"Build documentation for package PKG-DESC from documentation source in FILE.
FILE can be an Org file, indicated by its \".org\" extension,
otherwise it's assumed to be an Info file."
(let* ((pkg-name (package-desc-name pkg-desc))
(default-directory (package-desc-dir pkg-desc))
(output (expand-file-name (format "%s.info" pkg-name)))
clean-up)
(when (string-match-p "\\.org\\'" file)
(require 'ox)
(require 'ox-texinfo)
(with-temp-buffer
(insert-file-contents file)
(setq file (make-temp-file "ox-texinfo-"))
(org-export-to-file 'texinfo file)
(setq clean-up t)))
(with-current-buffer (get-buffer-create " *package-vc doc*")
(erase-buffer)
(cond
((/= 0 (call-process "makeinfo" nil t nil
"--no-split" file "-o" output))
(message "Failed to build manual %s, see buffer %S"
file (buffer-name)))
((/= 0 (call-process "install-info" nil t nil
output (expand-file-name "dir")))
(message "Failed to install manual %s, see buffer %S"
output (buffer-name)))
((kill-buffer))))
(when clean-up
(delete-file file))))
(defun package-vc--unpack-1 (pkg-desc pkg-dir)
"Prepare PKG-DESC that is already checked-out in PKG-DIR.
This includes downloading missing dependencies, generating
autoloads, generating a package description file (used to
identify a package as a source package later on), building
documentation and marking the package as installed."
;; Remove any previous instance of PKG-DESC from `package-alist'
(let ((pkgs (assq (package-desc-name pkg-desc) package-alist)))
(when pkgs
(setf (cdr pkgs) (seq-remove #'package-vc-p (cdr pkgs)))))
;; In case the package was installed directly from source, the
;; dependency list wasn't know beforehand, and they might have
;; to be installed explicitly.
(let ((deps '()))
(dolist (file (directory-files pkg-dir t "\\.el\\'" t))
(with-temp-buffer
(insert-file-contents file)
(when-let* ((require-lines (lm-header-multiline "package-requires")))
(thread-last
(mapconcat #'identity require-lines " ")
package-read-from-string
package--prepare-dependencies
(nconc deps)
(setq deps)))))
(dolist (dep deps)
(cl-callf version-to-list (cadr dep)))
(package-download-transaction
(package-compute-transaction nil (delete-dups deps))))
(let ((default-directory (file-name-as-directory pkg-dir))
(pkg-file (expand-file-name (package--description-file pkg-dir) pkg-dir)))
;; Generate autoloads
(let* ((name (package-desc-name pkg-desc))
(auto-name (format "%s-autoloads.el" name))
(extras (package-desc-extras pkg-desc))
(lisp-dir (alist-get :lisp-dir extras)))
(package-generate-autoloads
name (file-name-concat pkg-dir lisp-dir))
(when lisp-dir
(write-region
(with-temp-buffer
(insert ";; Autoload indirection for package-vc\n\n")
(prin1 `(load (expand-file-name
,(file-name-concat lisp-dir auto-name)
(or (and load-file-name
(file-name-directory load-file-name))
(car load-path))))
(current-buffer))
(buffer-string))
nil (expand-file-name auto-name pkg-dir))))
;; Generate package file
(package-vc--generate-description-file pkg-desc pkg-file)
;; Detect a manual
(when-let ((pkg-spec (package-vc--desc->spec pkg-desc))
((executable-find "install-info")))
(dolist (doc-file (ensure-list (plist-get pkg-spec :doc)))
(package-vc--build-documentation pkg-desc doc-file))))
;; Update package-alist.
(let ((new-desc (package-load-descriptor pkg-dir)))
;; Activation has to be done before compilation, so that if we're
;; upgrading and macros have changed we load the new definitions
;; before compiling.
(when (package-activate-1 new-desc :reload :deps)
;; FIXME: Compilation should be done as a separate, optional, step.
;; E.g. for multi-package installs, we should first install all packages
;; and then compile them.
(package--compile new-desc)
(when package-native-compile
(package--native-compile-async new-desc))
;; After compilation, load again any files loaded by
;; `activate-1', so that we use the byte-compiled definitions.
(package--reload-previously-loaded new-desc)))
;; Mark package as selected
(package--save-selected-packages
(cons (package-desc-name pkg-desc)
package-selected-packages))
;; Confirm that the installation was successful
(let ((main-file (package-vc--main-file pkg-desc)))
(message "Source package `%s' installed (Version %s, Revision %S)."
(package-desc-name pkg-desc)
(lm-with-file main-file
(package-strip-rcs-id
(or (lm-header "package-version")
(lm-header "version"))))
(vc-working-revision main-file)))
t)
(defun package-vc--guess-backend (url)
"Guess the VC backend for URL.
This function will internally query `package-vc-heuristic-alist'
and return nil if it cannot reasonably guess."
(and url (alist-get url package-vc-heuristic-alist
nil nil #'string-match-p)))
(defun package-vc--clone (pkg-desc pkg-spec dir rev)
"Clone the package PKG-DESC whose spec is PKG-SPEC into the directory DIR.
REV specifies a specific revision to checkout. This overrides the `:branch'
attribute in PKG-SPEC."
(pcase-let* ((name (package-desc-name pkg-desc))
((map :url :branch) pkg-spec))
;; Clone the repository into `repo-dir' if necessary
(unless (file-exists-p dir)
(make-directory (file-name-directory dir) t)
(let ((backend (or (plist-get pkg-spec :vc-backend)
(package-vc--query-spec pkg-desc :vc-backend)
(package-vc--guess-backend url)
(plist-get (alist-get (package-desc-archive pkg-desc)
package-vc--archive-data-alist
nil nil #'string=)
:vc-backend)
package-vc-default-backend)))
(unless (vc-clone url backend dir
(or (and (not (eq rev :last-release)) rev) branch))
(error "Failed to clone %s from %s" name url))))
;; Check out the latest release if requested
(when (eq rev :last-release)
(if-let ((release-rev (package-vc--release-rev pkg-desc)))
(vc-retrieve-tag dir release-rev)
(message "No release revision was found, continuing...")))))
(defun package-vc--unpack (pkg-desc pkg-spec &optional rev)
"Install the package described by PKG-DESC.
PKG-SPEC is a package specification, a property list describing
how to fetch and build the package. See `package-vc--archive-spec-alist'
for details. The optional argument REV specifies a specific revision to
checkout. This overrides the `:branch' attribute in PKG-SPEC."
(pcase-let* (((map :lisp-dir) pkg-spec)
(name (package-desc-name pkg-desc))
(dirname (package-desc-full-name pkg-desc))
(pkg-dir (expand-file-name dirname package-user-dir)))
(setf (package-desc-dir pkg-desc) pkg-dir)
(when (file-exists-p pkg-dir)
(if (yes-or-no-p "Overwrite previous checkout?")
(package--delete-directory pkg-dir)
(error "There already exists a checkout for %s" name)))
(package-vc--clone pkg-desc pkg-spec pkg-dir rev)
(when lisp-dir
(push (cons :lisp-dir lisp-dir)
(package-desc-extras pkg-desc)))
(package-vc--unpack-1 pkg-desc pkg-dir)))
(defun package-vc--read-package-name (prompt &optional allow-url installed)
"Query the user for a source package and return a name with PROMPT.
If the optional argument ALLOW-URL is non-nil, the user is also
allowed to specify a non-package name. If the optional argument
INSTALLED is non-nil, the selection will be filtered down to
source packages that have already been installed."
(package-vc--archives-initialize)
(completing-read prompt (if installed package-alist package-archive-contents)
(if installed
(lambda (pkg) (package-vc-p (cadr pkg)))
(lambda (pkg)
(or (package-vc--desc->spec (cadr pkg))
;; If we have no explicit VC data, we can try a kind of
;; heuristic and use the URL header, that might already be
;; pointing towards a repository, and use that as a backup
(and-let* ((extras (package-desc-extras (cadr pkg)))
(url (alist-get :url extras))
((package-vc--guess-backend url)))))))
(not allow-url)))
(defun package-vc--read-package-desc (prompt &optional installed)
"Query the user for a source package and return a description with PROMPT.
If the optional argument INSTALLED is non-nil, the selection will
be filtered down to source packages that have already been
installed, and the package description will be that of an
installed package."
(cadr (assoc (package-vc--read-package-name prompt nil installed)
(if installed package-alist package-archive-contents)
#'string=)))
;;;###autoload
(defun package-vc-update-all ()
"Attempt to update all installed VC packages."
(interactive)
(dolist (package package-alist)
(dolist (pkg-desc (cdr package))
(when (package-vc-p pkg-desc)
(package-vc-update pkg-desc))))
(message "Done updating packages."))
;;;###autoload
(defun package-vc-update (pkg-desc)
"Attempt to update the package PKG-DESC."
(interactive (list (package-vc--read-package-desc "Update source package: " t)))
;; HACK: To run `package-vc--unpack-1' after checking out the new
;; revision, we insert a hook into `vc-post-command-functions', and
;; remove it right after it ran. To avoid running the hook multiple
;; times or even for the wrong repository (as `vc-pull' is often
;; asynchronous), we extract the relevant arguments using a pseudo
;; filter for `vc-filter-command-function', executed only for the
;; side effect, and store them in the lexical scope. When the hook
;; is run, we check if the arguments are the same (`eq') as the ones
;; previously extracted, and only in that case will be call
;; `package-vc--unpack-1'. Ugh...
;;
;; If there is a better way to do this, it should be done.
(cl-assert (package-vc-p pkg-desc))
(letrec ((pkg-dir (package-desc-dir pkg-desc))
(vc-flags)
(vc-filter-command-function
(lambda (command file-or-list flags)
(setq vc-flags flags)
(list command file-or-list flags)))
(post-upgrade
(lambda (_command _file-or-list flags)
(when (and (file-equal-p pkg-dir default-directory)
(eq flags vc-flags))
(unwind-protect
(with-demoted-errors "Failed to activate: %S"
(package-vc--unpack-1 pkg-desc pkg-dir))
(remove-hook 'vc-post-command-functions post-upgrade))))))
(add-hook 'vc-post-command-functions post-upgrade)
(with-demoted-errors "Failed to fetch: %S"
(let ((default-directory pkg-dir))
(vc-pull)))))
(defun package-vc--archives-initialize ()
"Initialize package.el and fetch package specifications."
(package--archives-initialize)
(unless package-vc--archive-data-alist
(package-vc--download-and-read-archives)))
(defun package-vc--release-rev (pkg-desc)
"Return the latest revision that bumps the \"Version\" tag for PKG-DESC.
If no such revision can be found, return nil."
(with-current-buffer (find-file-noselect (package-vc--main-file pkg-desc))
(vc-buffer-sync)
(save-excursion
(goto-char (point-min))
(let ((case-fold-search t))
(when (cond
((re-search-forward
(concat (lm-get-header-re "package-version") ".*$")
(lm-code-start) t))
((re-search-forward
(concat (lm-get-header-re "version") ".*$")
(lm-code-start) t)))
(ignore-error vc-not-supported
(vc-call-backend (vc-backend (buffer-file-name))
'last-change
(buffer-file-name)
(line-number-at-pos nil t))))))))
;;;###autoload
(defun package-vc-install (package &optional name rev backend)
"Fetch a PACKAGE and set it up for using with Emacs.
If PACKAGE is a string containing an URL, download the package
from the repository at that URL; the function will try to guess
the name of the package from the URL. This can be overridden by
passing the optional argument NAME. If PACKAGE is a cons-cell,
it should have the form (NAME . SPEC), where NAME is a symbol
indicating the package name and SPEC is a plist as described in
`package-vc-selected-packages'. Otherwise PACKAGE should be a
symbol whose name is the package name, and the URL for the
package will be taken from the package's metadata.
By default, this function installs the last version of the package
available from its repository, but if REV is given and non-nil, it
specifies the revision to install. If REV has the special value
`:last-release' (interactively, the prefix argument), that stands
for the last released version of the package.
Optional argument BACKEND specifies the VC backend to use for cloning
the package's repository; this is only possible if NAME-OR-URL is a URL,
a string. If BACKEND is omitted or nil, the function
uses `package-vc-heuristic-alist' to guess the backend.
Note that by default, a source package will be prioritized over a
regular package, but it will not remove a source package."
(interactive
(progn
;; Initialize the package system to get the list of package
;; symbols for completion.
(package-vc--archives-initialize)
(let* ((name-or-url (package-vc--read-package-name
"Fetch and install package: " t))
(name (file-name-base name-or-url)))
(list name-or-url (intern (string-remove-prefix "emacs-" name))
(and current-prefix-arg :last-release)))))
(package-vc--archives-initialize)
(cond
((null package)
(signal 'wrong-type-argument nil))
((consp package)
(package-vc--unpack
(package-desc-create :name (car package)
:kind 'vc)
(cdr package)
rev))
((and-let* (((stringp package))
(backend (or backend (package-vc--guess-backend package))))
(package-vc--unpack
(package-desc-create
:name (or name (intern (file-name-base package)))
:kind 'vc)
(list :vc-backend backend :url package)
rev)))
((and-let* ((desc (assoc package package-archive-contents #'string=)))
(package-vc--unpack
(let ((copy (copy-package-desc (cadr desc))))
(setf (package-desc-kind copy) 'vc)
copy)
(or (package-vc--desc->spec (cadr desc))
(and-let* ((extras (package-desc-extras (cadr desc)))
(url (alist-get :url extras))
(backend (package-vc--guess-backend url)))
(list :vc-backend backend :url url))
(user-error "Package `%s' has no VC data" package))
rev)))
((user-error "Unknown package to fetch: %s" package))))
;;;###autoload
(defun package-vc-checkout (pkg-desc directory &optional rev)
"Clone the sources for PKG-DESC into DIRECTORY and visit that directory.
Unlike `package-vc-install', this does not yet set up the package
for use with Emacs; use `package-vc-link-directory' for setting
the package up after this function finishes.
Optional argument REV means to clone a specific version of the
package; it defaults to the last version available from the
package's repository. If REV has the special value
`:last-release' (interactively, the prefix argument), that stands
for the last released version of the package."
(interactive
(let* ((name (package-vc--read-package-name "Fetch package source: ")))
(list (cadr (assoc name package-archive-contents #'string=))
(read-file-name "Clone into new or empty directory: " nil nil t nil
(lambda (dir) (or (not (file-exists-p dir))
(directory-empty-p dir))))
(and current-prefix-arg :last-release))))
(package-vc--archives-initialize)
(let ((pkg-spec (or (package-vc--desc->spec pkg-desc)
(and-let* ((extras (package-desc-extras pkg-desc))
(url (alist-get :url extras))
(backend (package-vc--guess-backend url)))
(list :vc-backend backend :url url))
(user-error "Package `%s' has no VC data"
(package-desc-name pkg-desc)))))
(package-vc--clone pkg-desc pkg-spec directory rev)
(find-file directory)))
;;;###autoload
(defun package-vc-install-from-checkout (dir name)
"Set up the package NAME in DIR by linking it into the ELPA directory.
Interactively, prompt the user for DIR, which should be a directory
under version control, typically one created by `package-vc-checkout'.
If invoked interactively with a prefix argument, prompt the user
for the NAME of the package to set up. Otherwise infer the package
name from the base name of DIR."
(interactive (let ((dir (read-directory-name "Directory: ")))
(list dir
(if current-prefix-arg
(read-string "Package name: ")
(file-name-base (directory-file-name dir))))))
(unless (vc-responsible-backend dir)
(user-error "Directory %S is not under version control" dir))
(package-vc--archives-initialize)
(let* ((name (or name (file-name-base (directory-file-name dir))))
(pkg-dir (expand-file-name name package-user-dir)))
(make-symbolic-link (expand-file-name dir) pkg-dir)
(package-vc--unpack-1
(package-desc-create
:name (intern name)
:kind 'vc)
(file-name-as-directory pkg-dir))))
;;;###autoload
(defun package-vc-rebuild (pkg-desc)
"Rebuild the installation for package given by PKG-DESC.
Rebuilding an installation means scraping for new autoload
cookies, re-compiling Emacs Lisp files, building and installing
any documentation, downloading any missing dependencies. This
command does not fetch new revisions from a remote server. That
is the responsibility of `package-vc-update'. Interactively,
prompt for the name of the package to rebuild."
(interactive (list (package-vc--read-package-desc "Rebuild package: " t)))
(package-vc--unpack-1 pkg-desc (package-desc-dir pkg-desc)))
;;;###autoload
(defun package-vc-prepare-patch (pkg-desc subject revisions)
"Send patch for REVISIONS to maintainer of the package PKG using SUBJECT.
The function uses `vc-prepare-patch', passing SUBJECT and
REVISIONS directly. PKG-DESC must be a package description.
Interactively, prompt for PKG-DESC, SUBJECT, and REVISIONS. When
invoked with a numerical prefix argument, use the last N
revisions. When invoked interactively in a Log View buffer with
marked revisions, use those."
(interactive
(list (package-vc--read-package-desc "Package to prepare a patch for: " t)
(and (not vc-prepare-patches-separately)
(read-string "Subject: " "[PATCH] " nil nil t))
(vc-prepare-patch-prompt-revisions)))
(let ((default-directory (package-desc-dir pkg-desc)))
(vc-prepare-patch (package-maintainers pkg-desc t)
subject revisions)))
(provide 'package-vc)
;;; package-vc.el ends here

View file

@ -146,6 +146,7 @@
(require 'cl-lib)
(eval-when-compile (require 'subr-x))
(eval-when-compile (require 'epg)) ;For setf accessors.
(eval-when-compile (require 'inline)) ;For `define-inline'
(require 'seq)
(require 'tabulated-list)
@ -346,21 +347,28 @@ default directory."
(defcustom package-check-signature 'allow-unsigned
"Non-nil means to check package signatures when installing.
More specifically the value can be:
- nil: package signatures are ignored.
- `allow-unsigned': install a package even if it is unsigned, but
if it is signed, we have the key for it, and OpenGPG is
installed, verify the signature.
- t: accept a package only if it comes with at least one verified signature.
- `all': same as t, except when the package has several signatures,
in which case we verify all the signatures.
This also applies to the \"archive-contents\" file that lists the
contents of the archive."
contents of the archive.
The value can be one of:
t Accept a package only if it comes with at least
one verified signature.
`all' Same as t, but verify all signatures if there
are more than one.
`allow-unsigned' Install a package even if it is unsigned,
but verify the signature if possible (that
is, if it is signed, we have the key for it,
and GnuPG is installed).
nil Package signatures are ignored."
:type '(choice (const :value nil :tag "Never")
(const :value allow-unsigned :tag "Allow unsigned")
(const :value t :tag "Check always")
(const :value all :tag "Check all signatures"))
(const :value all :tag "Check always (all signatures)"))
:risky t
:version "27.1")
@ -449,6 +457,11 @@ synchronously."
(defvar package--default-summary "No description available.")
(define-inline package-vc-p (pkg-desc)
"Return non-nil if PKG-DESC is a source package."
(inline-letevals (pkg-desc)
(inline-quote (eq (package-desc-kind ,pkg-desc) 'vc))))
(cl-defstruct (package-desc
;; Rename the default constructor from `make-package-desc'.
(:constructor package-desc-create)
@ -461,14 +474,18 @@ synchronously."
&rest rest-plist
&aux
(name (intern name-string))
(version (version-to-list version-string))
(version (if (eq (car-safe version-string) 'vc)
(version-to-list (cdr version-string))
(version-to-list version-string)))
(reqs (mapcar (lambda (elt)
(list (car elt)
(version-to-list (cadr elt))))
(if (eq 'quote (car requirements))
(nth 1 requirements)
requirements)))
(kind (plist-get rest-plist :kind))
(kind (if (eq (car-safe version-string) 'vc)
'vc
(plist-get rest-plist :kind)))
(archive (plist-get rest-plist :archive))
(extras (let (alist)
(while rest-plist
@ -560,9 +577,11 @@ This is, approximately, the inverse of `version-to-list'.
(defun package-desc-full-name (pkg-desc)
"Return full name of package-desc object PKG-DESC.
This is the name of the package with its version appended."
(format "%s-%s"
(package-desc-name pkg-desc)
(package-version-join (package-desc-version pkg-desc))))
(if (package-vc-p pkg-desc)
(symbol-name (package-desc-name pkg-desc))
(format "%s-%s"
(package-desc-name pkg-desc)
(package-version-join (package-desc-version pkg-desc)))))
(defun package-desc-suffix (pkg-desc)
"Return file-name extension of package-desc object PKG-DESC.
@ -593,6 +612,25 @@ package."
"Return the priority of the archive of package-desc object PKG-DESC."
(package-archive-priority (package-desc-archive pkg-desc)))
(defun package--parse-elpaignore (pkg-desc)
"Return the of regular expression to match files ignored by PKG-DESC."
(let* ((pkg-dir (file-name-as-directory (package-desc-dir pkg-desc)))
(ignore (expand-file-name ".elpaignore" pkg-dir))
files)
(when (file-exists-p ignore)
(with-temp-buffer
(insert-file-contents ignore)
(goto-char (point-min))
(while (not (eobp))
(push (wildcard-to-regexp
(let ((line (buffer-substring
(line-beginning-position)
(line-end-position))))
(file-name-concat pkg-dir (string-trim-left line "/"))))
files)
(forward-line)))
files)))
(cl-defstruct (package--bi-desc
(:constructor package-make-builtin (version summary))
(:type vector))
@ -641,6 +679,8 @@ loaded and/or activated, customize `package-load-list'.")
;; `package-load-all-descriptors', which ultimately populates the
;; `package-alist' variable.
(declare-function package-vc-version "package-vc" (pkg))
(defun package-process-define-package (exp)
"Process define-package expression EXP and push it to `package-alist'.
EXP should be a form read from a foo-pkg.el file.
@ -669,6 +709,8 @@ are sorted with the highest version first."
nil)))
new-pkg-desc)))
(declare-function package-vc-commit "package-vc" (pkg))
(defun package-load-descriptor (pkg-dir)
"Load the package description file in directory PKG-DIR.
Create a new `package-desc' object, add it to `package-alist' and
@ -699,11 +741,9 @@ description file containing a call to `define-package', which
updates `package-alist'."
(dolist (dir (cons package-user-dir package-directory-list))
(when (file-directory-p dir)
(dolist (subdir (directory-files dir))
(unless (equal subdir "..")
(let ((pkg-dir (expand-file-name subdir dir)))
(when (file-directory-p pkg-dir)
(package-load-descriptor pkg-dir))))))))
(dolist (pkg-dir (directory-files dir t "\\`[^.]" t))
(when (file-directory-p pkg-dir)
(package-load-descriptor pkg-dir))))))
(defun package--alist ()
"Return `package-alist', after computing it if needed."
@ -828,8 +868,7 @@ byte-compilation of the new package to fail."
If DEPS is non-nil, also activate its dependencies (unless they
are already activated).
If RELOAD is non-nil, also `load' any files inside the package which
correspond to previously loaded files (those returned by
`package--list-loaded-files')."
correspond to previously loaded files."
(let* ((name (package-desc-name pkg-desc))
(pkg-dir (package-desc-dir pkg-desc)))
(unless pkg-dir
@ -867,14 +906,22 @@ correspond to previously loaded files (those returned by
(defun package--get-activatable-pkg (pkg-name)
;; Is "activatable" a word?
(let ((pkg-descs (cdr (assq pkg-name package-alist))))
(let ((pkg-descs (sort (cdr (assq pkg-name package-alist))
(lambda (p1 p2)
(let ((v1 (package-desc-version p1))
(v2 (package-desc-version p2)))
(or
;; Prefer source packages.
(package-vc-p p1)
(package-vc-p p2)
;; Prefer builtin packages.
(package-disabled-p p1 v1)
(not (package-disabled-p p2 v2))))))))
;; Check if PACKAGE is available in `package-alist'.
(while
(when pkg-descs
(let ((available-version (package-desc-version (car pkg-descs))))
(or (package-disabled-p pkg-name available-version)
;; Prefer a builtin package.
(package-built-in-p pkg-name available-version))))
(package-disabled-p pkg-name available-version)))
(setq pkg-descs (cdr pkg-descs)))
(car pkg-descs)))
@ -923,7 +970,7 @@ untar into a directory named DIR; otherwise, signal an error."
(or (string-match regexp name)
;; Tarballs created by some utilities don't list
;; directories with a trailing slash (Bug#13136).
(and (string-equal dir name)
(and (string-equal (expand-file-name dir) name)
(eq (tar-header-link-type tar-data) 5))
(error "Package does not untar cleanly into directory %s/" dir)))))
(tar-untar-buffer))
@ -952,7 +999,7 @@ untar into a directory named DIR; otherwise, signal an error."
;; indistinguishable from a `tar' or a `single'. Let's make
;; things simple by ensuring we're one of them.
(setf (package-desc-kind pkg-desc)
(if (> (length file-list) 1) 'tar 'single))))
(if (length> file-list 1) 'tar 'single))))
('tar
(make-directory package-user-dir t)
(let* ((default-directory (file-name-as-directory package-user-dir)))
@ -1015,6 +1062,7 @@ untar into a directory named DIR; otherwise, signal an error."
"\n")
nil pkg-file nil 'silent))))
;;;; Autoload
(declare-function autoload-rubric "autoload" (file &optional type feature))
@ -1042,10 +1090,15 @@ untar into a directory named DIR; otherwise, signal an error."
(backup-inhibited t)
(version-control 'never))
(loaddefs-generate
pkg-dir output-file
nil
"(add-to-list 'load-path (directory-file-name
(or (file-name-directory #$) (car load-path))))")
pkg-dir output-file nil
(prin1-to-string
'(add-to-list
'load-path
;; Add the directory that will contain the autoload file to
;; the load path. We don't hard-code `pkg-dir', to avoid
;; issues if the package directory is moved around.
(or (and load-file-name (file-name-directory load-file-name))
(car load-path)))))
(let ((buf (find-buffer-visiting output-file)))
(when buf (kill-buffer buf)))
auto-name))
@ -1062,11 +1115,13 @@ untar into a directory named DIR; otherwise, signal an error."
;;;; Compilation
(defvar warning-minimum-level)
(defvar byte-compile-ignore-files)
(defun package--compile (pkg-desc)
"Byte-compile installed package PKG-DESC.
This assumes that `pkg-desc' has already been activated with
`package-activate-1'."
(let ((warning-minimum-level :error)
(let ((byte-compile-ignore-files (package--parse-elpaignore pkg-desc))
(warning-minimum-level :error)
(load-path load-path))
(byte-recompile-directory (package-desc-dir pkg-desc) 0 t)))
@ -1185,8 +1240,12 @@ Return the pkg-desc, with desc-kind set to KIND."
"Find package information for a tar file.
The return result is a `package-desc'."
(cl-assert (derived-mode-p 'tar-mode))
(let* ((dir-name (file-name-directory
(tar-header-name (car tar-parse-info))))
(let* ((dir-name (named-let loop
((filename (tar-header-name (car tar-parse-info))))
(let ((dirname (file-name-directory filename)))
;; The first file can be in a subdir: look for the top.
(if dirname (loop (directory-file-name dirname))
(file-name-as-directory filename)))))
(desc-file (package--description-file dir-name))
(tar-desc (tar-get-file-descriptor (concat dir-name desc-file))))
(unless tar-desc
@ -1304,10 +1363,7 @@ is non-nil, don't propagate connection errors (does not apply to
errors signaled by ERROR-FORM or by BODY).
\(fn URL &key ASYNC FILE ERROR-FORM NOERROR &rest BODY)"
(declare (indent defun)
;; FIXME: This should be something like
;; `form def-body &rest form', but that doesn't work.
(debug (form &rest sexp)))
(declare (indent defun) (debug (sexp body)))
(while (keywordp (car body))
(setq body (cdr (cdr body))))
`(package--with-response-buffer-1 ,url (lambda () ,@body)
@ -1591,13 +1647,19 @@ This is the value of `package-archive-priorities' last time
by arbitrary functions to decide whether it is necessary to call
it again.")
(defvar package-read-archive-hook (list #'package-read-archive-contents)
"List of functions to call to read the archive contents.
Each function must take an optional argument, a symbol indicating
what archive to read in. The symbol ought to be a key in
`package-archives'.")
(defun package-read-all-archive-contents ()
"Read cached archive file for all archives in `package-archives'.
If successful, set or update `package-archive-contents'."
(setq package-archive-contents nil)
(setq package--old-archive-priorities package-archive-priorities)
(dolist (archive package-archives)
(package-read-archive-contents (car archive))))
(run-hook-with-args 'package-read-archive-hook (car archive))))
;;;; Package Initialize
@ -1723,9 +1785,14 @@ Once it's empty, run `package--post-download-archives-hook'."
ARCHIVE should be a cons cell of the form (NAME . LOCATION),
similar to an entry in `package-alist'. Save the cached copy to
\"archives/NAME/FILE\" in `package-user-dir'."
;; The downloaded archive contents will be read as part of
;; `package--update-downloads-in-progress'.
(when async
(cl-pushnew (cons archive file) package--downloads-in-progress
:test #'equal))
(package--with-response-buffer (cdr archive) :file file
:async async
:error-form (package--update-downloads-in-progress archive)
:error-form (package--update-downloads-in-progress (cons archive file))
(let* ((location (cdr archive))
(name (car archive))
(content (buffer-string))
@ -1738,10 +1805,10 @@ similar to an entry in `package-alist'. Save the cached copy to
;; If we don't care about the signature, save the file and
;; we're done.
(progn
(cl-assert (not enable-multibyte-characters))
(let ((coding-system-for-write 'binary))
(write-region content nil local-file nil 'silent))
(package--update-downloads-in-progress archive))
(cl-assert (not enable-multibyte-characters))
(let ((coding-system-for-write 'binary))
(write-region content nil local-file nil 'silent))
(package--update-downloads-in-progress (cons archive file)))
;; If we care, check it (perhaps async) and *then* write the file.
(package--check-signature
location file content async
@ -1754,7 +1821,7 @@ similar to an entry in `package-alist'. Save the cached copy to
(when good-sigs
(write-region (mapconcat #'epg-signature-to-string good-sigs "\n")
nil (concat local-file ".signed") nil 'silent)))
(lambda () (package--update-downloads-in-progress archive))))))))
(lambda () (package--update-downloads-in-progress (cons archive file)))))))))
(defun package--download-and-read-archives (&optional async)
"Download descriptions of all `package-archives' and read them.
@ -1762,17 +1829,17 @@ Populate `package-archive-contents' with the result.
If optional argument ASYNC is non-nil, perform the downloads
asynchronously."
;; The downloaded archive contents will be read as part of
;; `package--update-downloads-in-progress'.
(dolist (archive package-archives)
(cl-pushnew archive package--downloads-in-progress
:test #'equal))
(dolist (archive package-archives)
(condition-case-unless-debug nil
(package--download-one-archive archive "archive-contents" async)
(error (message "Failed to download `%s' archive."
(car archive))))))
(defvar package-refresh-contents-hook (list #'package--download-and-read-archives)
"List of functions to call to refresh the package archive.
Each function may take an optional argument indicating that the
operation ought to be executed asynchronously.")
;;;###autoload
(defun package-refresh-contents (&optional async)
"Download descriptions of all configured ELPA packages.
@ -1791,7 +1858,7 @@ downloads in the background."
(condition-case-unless-debug error
(package-import-keyring default-keyring)
(error (message "Cannot import default keyring: %S" (cdr error))))))
(package--download-and-read-archives async))
(run-hook-with-args 'package-refresh-contents-hook async))
;;; Dependency Management
@ -2025,9 +2092,9 @@ if all the in-between dependencies are also in PACKAGE-LIST."
(cdr (assoc (package-desc-archive desc) package-archives)))
(defun package-install-from-archive (pkg-desc)
"Download and install a tar package defined by PKG-DESC."
"Download and install a package defined by PKG-DESC."
;; This won't happen, unless the archive is doing something wrong.
(when (eq (package-desc-kind pkg-desc) 'dir)
(when (package-vc-p pkg-desc)
(error "Can't install directory package from archive"))
(let* ((location (package-archive-base pkg-desc))
(file (concat (package-desc-full-name pkg-desc)
@ -2165,17 +2232,22 @@ to install it but still mark it as selected."
(message "Package `%s' installed." name))
(message "`%s' is already installed" name))))
(declare-function package-vc-update "package-vc" (pkg))
;;;###autoload
(defun package-update (name)
"Update package NAME if a newer version exists."
(interactive
(list (completing-read
"Update package: " (package--updateable-packages) nil t)))
(let ((package (if (symbolp name)
name
(intern name))))
(package-delete (cadr (assq package package-alist)) 'force)
(package-install package 'dont-select)))
(let* ((package (if (symbolp name)
name
(intern name)))
(pkg-desc (cadr (assq package package-alist))))
(if (package-vc-p pkg-desc)
(package-vc-update pkg-desc)
(package-delete pkg-desc 'force)
(package-install package 'dont-select))))
(defun package--updateable-packages ()
;; Initialize the package system to get the list of package
@ -2185,12 +2257,13 @@ to install it but still mark it as selected."
#'car
(seq-filter
(lambda (elt)
(let ((available
(assq (car elt) package-archive-contents)))
(and available
(version-list-<
(package-desc-version (cadr elt))
(package-desc-version (cadr available))))))
(or (let ((available
(assq (car elt) package-archive-contents)))
(and available
(version-list-<
(package-desc-version (cadr elt))
(package-desc-version (cadr available)))))
(package-vc-p (cadr (assq (car elt) package-alist)))))
package-alist)))
;;;###autoload
@ -2347,15 +2420,19 @@ installed), maybe you need to \\[package-refresh-contents]")
pkg))
(declare-function comp-el-to-eln-filename "comp.c")
(defvar package-vc-repository-store)
(defun package--delete-directory (dir)
"Delete DIR recursively.
"Delete PKG-DESC directory DIR recursively.
Clean-up the corresponding .eln files if Emacs is native
compiled."
(when (featurep 'native-compile)
(cl-loop
for file in (directory-files-recursively dir "\\.el\\'")
do (comp-clean-up-stale-eln (comp-el-to-eln-filename file))))
(delete-directory dir t))
(if (file-symlink-p (directory-file-name dir))
(delete-file (directory-file-name dir))
(delete-directory dir t)))
(defun package-delete (pkg-desc &optional force nosave)
"Delete package PKG-DESC.
@ -2620,7 +2697,10 @@ Helper function for `describe-package'."
(incompatible-reason (package--incompatible-p desc))
(signed (if desc (package-desc-signed desc)))
(maintainer (cdr (assoc :maintainer extras)))
(authors (cdr (assoc :authors extras))))
(authors (cdr (assoc :authors extras)))
(news (and-let* ((file (expand-file-name "news" pkg-dir))
((file-readable-p file)))
file)))
(when (string= status "avail-obso")
(setq status "available obsolete"))
(when incompatible-reason
@ -2819,6 +2899,14 @@ Helper function for `describe-package'."
t)
(insert (or readme-string
"This package does not provide a description.")))))
;; Insert news if available.
(when news
(insert "\n" (make-separator-line) "\n"
(propertize "* News" 'face 'package-help-section-name)
"\n\n")
(insert-file-contents news))
;; Make library descriptions into links.
(goto-char start-of-description)
(package--describe-add-library-links)
@ -2909,6 +2997,7 @@ either a full name or nil, and EMAIL is a valid email address."
"r" #'revert-buffer
"~" #'package-menu-mark-obsolete-for-deletion
"w" #'package-browse-url
"b" #'package-report-bug
"x" #'package-menu-execute
"h" #'package-menu-quick-help
"H" #'package-menu-hide-package
@ -3067,6 +3156,7 @@ of these dependencies, similar to the list returned by
(signed (or (not package-list-unsigned)
(package-desc-signed pkg-desc))))
(cond
((package-vc-p pkg-desc) "source")
((eq dir 'builtin) "built-in")
((and lle (null held)) "disabled")
((stringp held)
@ -3155,8 +3245,9 @@ to their archives."
(if (not installed)
filtered-by-priority
(let ((ins-version (package-desc-version installed)))
(cl-remove-if (lambda (p) (version-list-= (package-desc-version p)
ins-version))
(cl-remove-if (lambda (p) (or (version-list-= (package-desc-version p)
ins-version)
(package-vc-p installed)))
filtered-by-priority))))))))
(defcustom package-hidden-regexps nil
@ -3358,6 +3449,11 @@ Return (PKG-DESC [NAME VERSION STATUS DOC])."
"Face used on the status and version of installed packages."
:version "25.1")
(defface package-status-from-source
'((t :inherit font-lock-negation-char-face))
"Face used on the status and version of installed packages."
:version "29.1")
(defface package-status-dependency
'((t :inherit package-status-installed))
"Face used on the status and version of dependency packages."
@ -3395,6 +3491,7 @@ Return (PKG-DESC [NAME VERSION STATUS DOC])."
("held" 'package-status-held)
("disabled" 'package-status-disabled)
("installed" 'package-status-installed)
("source" 'package-status-from-source)
("dependency" 'package-status-dependency)
("unsigned" 'package-status-unsigned)
("incompat" 'package-status-incompat)
@ -3406,9 +3503,14 @@ Return (PKG-DESC [NAME VERSION STATUS DOC])."
follow-link t
package-desc ,pkg
action package-menu-describe-package)
,(propertize (package-version-join
(package-desc-version pkg))
'font-lock-face face)
,(propertize
(if (package-vc-p pkg)
(progn
(require 'package-vc)
(package-vc-commit pkg))
(package-version-join
(package-desc-version pkg)))
'font-lock-face face)
,(propertize status 'font-lock-face face)
,@(if (cdr package-archives)
(list (propertize (or (package-desc-archive pkg) "")
@ -3483,7 +3585,7 @@ If optional arg BUTTON is non-nil, describe its associated package."
(interactive "p" package-menu-mode)
(package--ensure-package-menu-mode)
(if (member (package-menu-get-status)
'("installed" "dependency" "obsolete" "unsigned"))
'("installed" "source" "dependency" "obsolete" "unsigned"))
(tabulated-list-put-tag "D" t)
(forward-line)))
@ -3839,6 +3941,8 @@ This is used for `tabulated-list-format' in `package-menu-mode'."
((string= sB "installed") nil)
((string= sA "dependency") t)
((string= sB "dependency") nil)
((string= sA "source") t)
((string= sB "source") nil)
((string= sA "unsigned") t)
((string= sB "unsigned") nil)
((string= sA "held") t)
@ -4132,6 +4236,7 @@ packages."
"held"
"incompat"
"installed"
"source"
"new"
"unsigned")))
package-menu-mode)
@ -4203,22 +4308,22 @@ Unlike other filters, this leaves the marks intact."
(while (not (eobp))
(setq mark (char-after))
(unless (eq mark ?\s)
(setq pkg-id (tabulated-list-get-id))
(setq pkg-id (tabulated-list-get-id))
(setq entry (package-menu--print-info-simple pkg-id))
(push entry found-entries)
;; remember the mark
(push (cons pkg-id mark) marks))
(push entry found-entries)
;; remember the mark
(push (cons pkg-id mark) marks))
(forward-line))
(if found-entries
(progn
(setq tabulated-list-entries found-entries)
(package-menu--display t nil)
;; redo the marks, but we must remember the marks!!
(goto-char (point-min))
(while (not (eobp))
(setq mark (cdr (assq (tabulated-list-get-id) marks)))
(tabulated-list-put-tag (char-to-string mark) t)))
(user-error "No packages found")))))
;; redo the marks, but we must remember the marks!!
(goto-char (point-min))
(while (not (eobp))
(setq mark (cdr (assq (tabulated-list-get-id) marks)))
(tabulated-list-put-tag (char-to-string mark) t)))
(user-error "No packages found")))))
(defun package-menu-filter-upgradable ()
"Filter \"*Packages*\" buffer to show only upgradable packages."
@ -4400,11 +4505,22 @@ beginning of the line."
(package-version-join (package-desc-version package-desc))
(package-desc-summary package-desc))))
(defun package--query-desc (&optional alist)
"Query the user for a package or return the package at point.
The optional argument ALIST must consist of elements with the
form (PKG-NAME PKG-DESC). If not specified, it will default to
`package-alist'."
(or (tabulated-list-get-id)
(let ((alist (or alist package-alist)))
(cadr (assoc (completing-read "Package: " alist nil t)
alist #'string=)))))
(defun package-browse-url (desc &optional secondary)
"Open the website of the package under point in a browser.
`browse-url' is used to determine the browser to be used.
If SECONDARY (interactively, the prefix), use the secondary browser."
(interactive (list (tabulated-list-get-id)
`browse-url' is used to determine the browser to be used. If
SECONDARY (interactively, the prefix), use the secondary browser.
DESC must be a `package-desc' object."
(interactive (list (package--query-desc)
current-prefix-arg)
package-menu-mode)
(unless desc
@ -4413,9 +4529,56 @@ If SECONDARY (interactively, the prefix), use the secondary browser."
(unless url
(user-error "No website for %s" (package-desc-name desc)))
(if secondary
(funcall browse-url-secondary-browser-function url)
(funcall browse-url-secondary-browser-function url)
(browse-url url))))
(declare-function ietf-drums-parse-address "ietf-drums"
(string &optional decode))
(defun package-maintainers (pkg-desc &optional no-error)
"Return an email address for the maintainers of PKG-DESC.
The email address may contain commas, if there are multiple
maintainers. If no maintainers are found, an error will be
signaled. If the optional argument NO-ERROR is non-nil no error
will be signaled in that case."
(unless (package-desc-p pkg-desc)
(error "Invalid package description: %S" pkg-desc))
(let* ((name (package-desc-name pkg-desc))
(extras (package-desc-extras pkg-desc))
(maint (alist-get :maintainer extras)))
(cond
((and (null maint) (null no-error))
(user-error "Package `%s' has no explicit maintainer" name))
((and (not (progn
(require 'ietf-drums)
(ietf-drums-parse-address maint)))
(null no-error))
(user-error "Package `%s' has no maintainer address" name))
((not (null maint))
(with-temp-buffer
(package--print-email-button maint)
(string-trim (substring-no-properties (buffer-string))))))))
(defun package-report-bug (desc)
"Prepare a message to send to the maintainers of a package.
DESC must be a `package-desc' object."
(interactive (list (package--query-desc package-alist))
package-menu-mode)
(let ((maint (package-maintainers desc))
(name (symbol-name (package-desc-name desc)))
vars)
(dolist-with-progress-reporter (group custom-current-group-alist)
"Scanning for modified user options..."
(dolist (ent (get (cdr group) 'custom-group))
(when (and (custom-variable-p (car ent))
(boundp (car ent))
(not (eq (custom--standard-value (car ent))
(default-toplevel-value (car ent))))
(file-in-directory-p (car group) (package-desc-dir desc)))
(push (car ent) vars))))
(dlet ((reporter-prompt-for-summary-p t))
(reporter-submit-bug-report maint name vars))))
;;;; Introspection
(defun package-get-descriptor (pkg-name)

View file

@ -211,6 +211,7 @@ Except for Lisp syntax this is the same as `reb-regexp'.")
(defvar reb-valid-string ""
"String in mode line showing validity of RE.")
(put 'reb-valid-string 'risky-local-variable t)
(defconst reb-buffer "*RE-Builder*"
"Buffer to use for the RE Builder.")
@ -308,13 +309,13 @@ Except for Lisp syntax this is the same as `reb-regexp'.")
"Return t if display is capable of displaying colors."
(eq 'color (frame-parameter nil 'display-type)))
(defsubst reb-lisp-syntax-p ()
(defun reb-lisp-syntax-p ()
"Return non-nil if RE Builder uses `rx' syntax."
(eq reb-re-syntax 'rx))
(defmacro reb-target-binding (symbol)
(defun reb-target-value (symbol)
"Return binding for SYMBOL in the RE Builder target buffer."
`(with-current-buffer reb-target-buffer ,symbol))
(buffer-local-value symbol reb-target-buffer))
(defun reb-initialize-buffer ()
"Initialize the current buffer as a RE Builder buffer."
@ -440,7 +441,7 @@ provided in the Commentary section of this library."
(interactive)
(reb-update-regexp)
(let ((re (with-output-to-string
(print (reb-target-binding reb-regexp)))))
(print (reb-target-value 'reb-regexp)))))
(setq re (substring re 1 (1- (length re))))
(setq re (string-replace "\n" "\\n" re))
(kill-new re)
@ -518,12 +519,17 @@ An actual update is only done if the regexp has changed or if the
optional fourth argument FORCE is non-nil."
(let ((prev-valid reb-valid-string)
(new-valid
(condition-case nil
(condition-case err
(progn
(when (or (reb-update-regexp) force)
(reb-do-update))
"")
(error " *invalid*"))))
(error (propertize
(format " %s"
(if (and (consp (cdr err)) (stringp (cadr err)))
(format "%s: %s" (car err) (cadr err))
(car err)))
'face 'font-lock-warning-face)))))
(setq reb-valid-string new-valid)
(force-mode-line-update)
@ -554,7 +560,7 @@ optional fourth argument FORCE is non-nil."
(if reb-subexp-mode
(format " (subexp %s)" (or reb-subexp-displayed "-"))
"")
(if (not (reb-target-binding case-fold-search))
(if (not (reb-target-value 'case-fold-search))
" Case"
"")))
(force-mode-line-update))
@ -600,7 +606,7 @@ optional fourth argument FORCE is non-nil."
(defun reb-insert-regexp ()
"Insert current RE."
(let ((re (or (reb-target-binding reb-regexp)
(let ((re (or (reb-target-value 'reb-regexp)
(reb-empty-regexp))))
(cond ((eq reb-re-syntax 'read)
(print re (current-buffer)))
@ -608,7 +614,7 @@ optional fourth argument FORCE is non-nil."
(insert "\n\"" re "\""))
;; For the Lisp syntax we need the "source" of the regexp
((reb-lisp-syntax-p)
(insert (or (reb-target-binding reb-regexp-src)
(insert (or (reb-target-value 'reb-regexp-src)
(reb-empty-regexp)))))))
(defun reb-cook-regexp (re)
@ -627,9 +633,8 @@ Return t if the (cooked) expression changed."
(prog1
(not (string= oldre re))
(setq reb-regexp re)
;; Only update the source re for the lisp formats
(when (reb-lisp-syntax-p)
(setq reb-regexp-src re-src)))))))
;; Update the source re for the Lisp formats.
(setq reb-regexp-src re-src))))))
;; And now the real core of the whole thing
@ -644,7 +649,7 @@ Return t if the (cooked) expression changed."
(defun reb-update-overlays (&optional subexp)
"Switch to `reb-target-buffer' and mark all matches of `reb-regexp'.
If SUBEXP is non-nil mark only the corresponding sub-expressions."
(let* ((re (reb-target-binding reb-regexp))
(let* ((re (reb-target-value 'reb-regexp))
(subexps (reb-count-subexps re))
(matches 0)
(submatches 0)

View file

@ -125,7 +125,7 @@
;;;###autoload
(defun read-multiple-choice (prompt choices &optional help-string show-help
long-form)
"Ask user to select an entry from CHOICES, promting with PROMPT.
"Ask user to select an entry from CHOICES, prompting with PROMPT.
This function allows to ask the user a multiple-choice question.
CHOICES should be a list of the form (KEY NAME [DESCRIPTION]).

View file

@ -63,8 +63,7 @@
;; preloaded. See also Bug#39761#26.
(defmacro seq-doseq (spec &rest body)
"Loop over a sequence.
Evaluate BODY with VAR bound to each element of SEQUENCE, in turn.
"Loop over a SEQUENCE, evaluating BODY with VAR bound to each of its elements.
Similar to `dolist' but can be applied to lists, strings, and vectors.
@ -95,7 +94,7 @@ name to be bound to the rest of SEQUENCE."
,@body))
(defmacro seq-setq (args sequence)
"Assign to the variables in ARGS the elements of SEQUENCE.
"Assign the elements of SEQUENCE to the variables in ARGS.
ARGS can also include the `&rest' marker followed by a variable
name to be bound to the rest of SEQUENCE."
@ -105,7 +104,7 @@ name to be bound to the rest of SEQUENCE."
;;; Basic seq functions that have to be implemented by new sequence types
(cl-defgeneric seq-elt (sequence n)
"Return Nth element of SEQUENCE."
"Return the Nth element of SEQUENCE."
(elt sequence n))
;; Default gv setters for `seq-elt'.
@ -118,7 +117,7 @@ name to be bound to the rest of SEQUENCE."
(setcar (nthcdr n sequence) store))
(cl-defgeneric seq-length (sequence)
"Return the number of elements of SEQUENCE."
"Return the number of elements in SEQUENCE."
(length sequence))
(defun seq-first (sequence)
@ -126,11 +125,12 @@ name to be bound to the rest of SEQUENCE."
(seq-elt sequence 0))
(defun seq-rest (sequence)
"Return a sequence of the elements of SEQUENCE except the first one."
"Return SEQUENCE with its first element removed."
(seq-drop sequence 1))
(cl-defgeneric seq-do (function sequence)
"Apply FUNCTION to each element of SEQUENCE, presumably for side effects.
"Apply FUNCTION to each element of SEQUENCE.
Presumably, FUNCTION has useful side effects.
Return SEQUENCE."
(mapc function sequence))
@ -216,8 +216,9 @@ the sequence, and its index within the sequence."
(mapcar function sequence))
(cl-defgeneric seq-mapn (function sequence &rest sequences)
"Like `seq-map' but FUNCTION is mapped over all SEQUENCES.
The arity of FUNCTION must match the number of SEQUENCES, and the
"Return the result of applying FUNCTION to each element of SEQUENCEs.
Like `seq-map', but FUNCTION is mapped over all SEQUENCEs.
The arity of FUNCTION must match the number of SEQUENCEs, and the
mapping stops on the shortest sequence.
Return a list of the results.
@ -232,7 +233,7 @@ Return a list of the results.
(nreverse result)))
(cl-defgeneric seq-drop (sequence n)
"Remove the first N elements of SEQUENCE and return the result.
"Remove the first N elements of SEQUENCE and return the resulting sequence.
The result is a sequence of the same type as SEQUENCE.
If N is a negative integer or zero, SEQUENCE is returned."
@ -243,7 +244,7 @@ If N is a negative integer or zero, SEQUENCE is returned."
;;;###autoload
(cl-defgeneric seq-take (sequence n)
"Take the first N elements of SEQUENCE and return the result.
"Return the sequence made of the first N elements of SEQUENCE.
The result is a sequence of the same type as SEQUENCE.
If N is a negative integer or zero, an empty sequence is
@ -252,14 +253,17 @@ returned."
(cl-defgeneric seq-drop-while (pred sequence)
"Remove the successive elements of SEQUENCE for which PRED returns non-nil.
PRED is a function of one argument. The result is a sequence of
the same type as SEQUENCE."
PRED is a function of one argument. The function keeps removing
elements from SEQUENCE until PRED returns nil for an element.
Value is a sequence of the same type as SEQUENCE."
(seq-drop sequence (seq--count-successive pred sequence)))
(cl-defgeneric seq-take-while (pred sequence)
"Take the successive elements of SEQUENCE for which PRED returns non-nil.
PRED is a function of one argument. The result is a sequence of
the same type as SEQUENCE."
PRED is a function of one argument. The function keeps collecting
elements from SEQUENCE and adding them to the result until PRED
returns nil for an element.
Value is a sequence of the same type as SEQUENCE."
(seq-take sequence (seq--count-successive pred sequence)))
(cl-defgeneric seq-empty-p (sequence)
@ -267,7 +271,7 @@ the same type as SEQUENCE."
(= 0 (seq-length sequence)))
(cl-defgeneric seq-sort (pred sequence)
"Sort SEQUENCE using PRED as comparison function.
"Sort SEQUENCE using PRED as the sorting comparison function.
The result is a sequence of the same type as SEQUENCE."
(let ((result (seq-sort pred (append sequence nil))))
(seq-into result (type-of sequence))))
@ -277,7 +281,7 @@ The result is a sequence of the same type as SEQUENCE."
;;;###autoload
(defun seq-sort-by (function pred sequence)
"Sort SEQUENCE using PRED as a comparison function.
"Sort SEQUENCE transformed by FUNCTION using PRED as the comparison function.
Elements of SEQUENCE are transformed by FUNCTION before being
sorted. FUNCTION must be a function of one argument."
(seq-sort (lambda (a b)
@ -300,7 +304,7 @@ sorted. FUNCTION must be a function of one argument."
(cl-defgeneric seq-concatenate (type &rest sequences)
"Concatenate SEQUENCES into a single sequence of type TYPE.
TYPE must be one of following symbols: vector, string or list.
TYPE must be one of following symbols: `vector', `string' or `list'.
\n(fn TYPE SEQUENCE...)"
(setq sequences (mapcar #'seq-into-sequence sequences))
@ -322,8 +326,8 @@ of sequence."
(cl-defgeneric seq-into (sequence type)
"Concatenate the elements of SEQUENCE into a sequence of type TYPE.
TYPE can be one of the following symbols: vector, string or
list."
TYPE can be one of the following symbols: `vector', `string' or
`list'."
(pcase type
(`vector (seq--into-vector sequence))
(`string (seq--into-string sequence))
@ -332,7 +336,7 @@ list."
;;;###autoload
(cl-defgeneric seq-filter (pred sequence)
"Return a list of all elements for which (PRED element) is non-nil in SEQUENCE."
"Return a list of all the elements in SEQUENCE for which PRED returns non-nil."
(let ((exclude (make-symbol "exclude")))
(delq exclude (seq-map (lambda (elt)
(if (funcall pred elt)
@ -342,13 +346,13 @@ list."
;;;###autoload
(cl-defgeneric seq-remove (pred sequence)
"Return a list of all the elements for which (PRED element) is nil in SEQUENCE."
"Return a list of all the elements in SEQUENCE for which PRED returns nil."
(seq-filter (lambda (elt) (not (funcall pred elt)))
sequence))
;;;###autoload
(cl-defgeneric seq-remove-at-position (sequence n)
"Return a copy of SEQUENCE where the element at N got removed.
"Return a copy of SEQUENCE with the element at index N removed.
N is the (zero-based) index of the element that should not be in
the result.
@ -381,7 +385,7 @@ If SEQUENCE is empty, return INITIAL-VALUE and FUNCTION is not called."
;;;###autoload
(cl-defgeneric seq-every-p (pred sequence)
"Return non-nil if (PRED element) is non-nil for all elements of SEQUENCE."
"Return non-nil if PRED returns non-nil for all the elements of SEQUENCE."
(catch 'seq--break
(seq-doseq (elt sequence)
(or (funcall pred elt)
@ -390,8 +394,8 @@ If SEQUENCE is empty, return INITIAL-VALUE and FUNCTION is not called."
;;;###autoload
(cl-defgeneric seq-some (pred sequence)
"Return non-nil if PRED is satisfied for at least one element of SEQUENCE.
If so, return the first non-nil value returned by PRED."
"Return non-nil if PRED returns non-nil for at least one element of SEQUENCE.
If the value is non-nil, it is the first non-nil value returned by PRED."
(catch 'seq--break
(seq-doseq (elt sequence)
(let ((result (funcall pred elt)))
@ -401,12 +405,12 @@ If so, return the first non-nil value returned by PRED."
;;;###autoload
(cl-defgeneric seq-find (pred sequence &optional default)
"Return the first element for which (PRED element) is non-nil in SEQUENCE.
If no element is found, return DEFAULT.
"Return the first element in SEQUENCE for which PRED returns non-nil.
If no such element is found, return DEFAULT.
Note that `seq-find' has an ambiguity if the found element is
identical to DEFAULT, as it cannot be known if an element was
found or not."
identical to DEFAULT, as in that case it is impossible to know
whether an element was found or not."
(catch 'seq--break
(seq-doseq (elt sequence)
(when (funcall pred elt)
@ -414,7 +418,7 @@ found or not."
default))
(cl-defgeneric seq-count (pred sequence)
"Return the number of elements for which (PRED element) is non-nil in SEQUENCE."
"Return the number of elements in SEQUENCE for which PRED returns non-nil."
(let ((count 0))
(seq-doseq (elt sequence)
(when (funcall pred elt)
@ -422,8 +426,8 @@ found or not."
count))
(cl-defgeneric seq-contains (sequence elt &optional testfn)
"Return the first element in SEQUENCE that is equal to ELT.
Equality is defined by the function TESTFN, which defaults to `equal'."
"Return the first element in SEQUENCE that is \"equal\" to ELT.
\"Equality\" is defined by the function TESTFN, which defaults to `equal'."
(declare (obsolete seq-contains-p "27.1"))
(seq-some (lambda (e)
(when (funcall (or testfn #'equal) elt e)
@ -431,8 +435,8 @@ Equality is defined by the function TESTFN, which defaults to `equal'."
sequence))
(cl-defgeneric seq-contains-p (sequence elt &optional testfn)
"Return non-nil if SEQUENCE contains an element equal to ELT.
Equality is defined by the function TESTFN, which defaults to `equal'."
"Return non-nil if SEQUENCE contains an element \"equal\" to ELT.
\"Equality\" is defined by the function TESTFN, which defaults to `equal'."
(catch 'seq--break
(seq-doseq (e sequence)
(let ((r (funcall (or testfn #'equal) e elt)))
@ -442,15 +446,16 @@ Equality is defined by the function TESTFN, which defaults to `equal'."
(cl-defgeneric seq-set-equal-p (sequence1 sequence2 &optional testfn)
"Return non-nil if SEQUENCE1 and SEQUENCE2 contain the same elements.
This does not depend on the order of the elements.
Equality is defined by the function TESTFN, which defaults to `equal'."
The order of the elements in the sequences is not important.
\"Equality\" of elements is defined by the function TESTFN, which
defaults to `equal'."
(and (seq-every-p (lambda (item1) (seq-contains-p sequence2 item1 testfn)) sequence1)
(seq-every-p (lambda (item2) (seq-contains-p sequence1 item2 testfn)) sequence2)))
;;;###autoload
(cl-defgeneric seq-position (sequence elt &optional testfn)
"Return the (zero-based) index of the first element in SEQUENCE equal to ELT.
Equality is defined by the function TESTFN, which defaults to `equal'."
"Return the (zero-based) index of the first element in SEQUENCE \"equal\" to ELT.
\"Equality\" is defined by the function TESTFN, which defaults to `equal'."
(let ((index 0))
(catch 'seq--break
(seq-doseq (e sequence)
@ -461,11 +466,11 @@ Equality is defined by the function TESTFN, which defaults to `equal'."
;;;###autoload
(cl-defgeneric seq-positions (sequence elt &optional testfn)
"Return indices for which (TESTFN (seq-elt SEQUENCE index) ELT) is non-nil.
"Return list of indices of SEQUENCE elements for which TESTFN returns non-nil.
TESTFN is a two-argument function which is passed each element of
SEQUENCE as first argument and ELT as second. TESTFN defaults to
`equal'.
TESTFN is a two-argument function which is called with each element of
SEQUENCE as the first argument and ELT as the second.
TESTFN defaults to `equal'.
The result is a list of (zero-based) indices."
(let ((result '()))
@ -479,7 +484,7 @@ The result is a list of (zero-based) indices."
;;;###autoload
(cl-defgeneric seq-uniq (sequence &optional testfn)
"Return a list of the elements of SEQUENCE with duplicates removed.
TESTFN is used to compare elements, or `equal' if TESTFN is nil."
TESTFN is used to compare elements, and defaults to `equal'."
(let ((result '()))
(seq-doseq (elt sequence)
(unless (seq-contains-p result elt testfn)
@ -514,15 +519,15 @@ TESTFN is used to compare elements, or `equal' if TESTFN is nil."
(nreverse result)))
(cl-defgeneric seq-mapcat (function sequence &optional type)
"Concatenate the result of applying FUNCTION to each element of SEQUENCE.
The result is a sequence of type TYPE, or a list if TYPE is nil."
"Concatenate the results of applying FUNCTION to each element of SEQUENCE.
The result is a sequence of type TYPE; TYPE defaults to `list'."
(apply #'seq-concatenate (or type 'list)
(seq-map function sequence)))
(cl-defgeneric seq-partition (sequence n)
"Return list of elements of SEQUENCE grouped into sub-sequences of length N.
The last sequence may contain less than N elements. If N is a
negative integer or 0, nil is returned."
negative integer or 0, the function returns nil."
(unless (< n 1)
(let ((result '()))
(while (not (seq-empty-p sequence))
@ -532,8 +537,9 @@ negative integer or 0, nil is returned."
;;;###autoload
(cl-defgeneric seq-union (sequence1 sequence2 &optional testfn)
"Return a list of all elements that appear in either SEQUENCE1 or SEQUENCE2.
Equality is defined by the function TESTFN, which defaults to `equal'."
"Return a list of all the elements that appear in either SEQUENCE1 or SEQUENCE2.
\"Equality\" of elements is defined by the function TESTFN, which
defaults to `equal'."
(let* ((accum (lambda (acc elt)
(if (seq-contains-p acc elt testfn)
acc
@ -544,8 +550,9 @@ Equality is defined by the function TESTFN, which defaults to `equal'."
;;;###autoload
(cl-defgeneric seq-intersection (sequence1 sequence2 &optional testfn)
"Return a list of the elements that appear in both SEQUENCE1 and SEQUENCE2.
Equality is defined by the function TESTFN, which defaults to `equal'."
"Return a list of all the elements that appear in both SEQUENCE1 and SEQUENCE2.
\"Equality\" of elements is defined by the function TESTFN, which
defaults to `equal'."
(seq-reduce (lambda (acc elt)
(if (seq-contains-p sequence2 elt testfn)
(cons elt acc)
@ -554,8 +561,9 @@ Equality is defined by the function TESTFN, which defaults to `equal'."
'()))
(cl-defgeneric seq-difference (sequence1 sequence2 &optional testfn)
"Return a list of the elements that appear in SEQUENCE1 but not in SEQUENCE2.
Equality is defined by the function TESTFN, which defaults to `equal'."
"Return list of all the elements that appear in SEQUENCE1 but not in SEQUENCE2.
\"Equality\" of elements is defined by the function TESTFN, which
defaults to `equal'."
(seq-reduce (lambda (acc elt)
(if (seq-contains-p sequence2 elt testfn)
acc
@ -591,7 +599,7 @@ SEQUENCE must be a sequence of numbers or markers."
(apply #'max (seq-into sequence 'list)))
(defun seq--count-successive (pred sequence)
"Count successive elements for which (PRED element) is non-nil in SEQUENCE."
"Count successive elements in SEQUENCE for which PRED returns non-nil."
(let ((n 0)
(len (seq-length sequence)))
(while (and (< n len)
@ -628,13 +636,13 @@ SEQUENCE must be a sequence of numbers or markers."
;; TODO: make public?
(defun seq--elt-safe (sequence n)
"Return element of SEQUENCE at the index N.
"Return the element of SEQUENCE whose zero-based index is N.
If no element is found, return nil."
(ignore-errors (seq-elt sequence n)))
;;;###autoload
(cl-defgeneric seq-random-elt (sequence)
"Return a random element from SEQUENCE.
"Return a randomly chosen element from SEQUENCE.
Signal an error if SEQUENCE is empty."
(if (seq-empty-p sequence)
(error "Sequence cannot be empty")
@ -681,8 +689,8 @@ Signal an error if SEQUENCE is empty."
(concat sequence)))
(defun seq-split (sequence length)
"Split SEQUENCE into a list of sub-sequences of at most LENGTH.
All the sub-sequences will be of LENGTH, except the last one,
"Split SEQUENCE into a list of sub-sequences of at most LENGTH elements.
All the sub-sequences will be LENGTH long, except the last one,
which may be shorter."
(when (< length 1)
(error "Sub-sequence length must be larger than zero"))
@ -696,7 +704,7 @@ which may be shorter."
(nreverse result)))
(defun seq-keep (function sequence)
"Apply FUNCTION to SEQUENCE and return all non-nil results."
"Apply FUNCTION to SEQUENCE and return the list of all the non-nil results."
(delq nil (seq-map function sequence)))
(provide 'seq)

View file

@ -833,7 +833,7 @@ A FUNC form can have any number of `:no-eval' (or `:no-value'),
(seq-set-equal-p
:eval (seq-set-equal-p '(1 2 3) '(3 1 2)))
(seq-some
:eval (seq-some #'cl-evenp '(1 2 3)))
:eval (seq-some #'floatp '(1 2.0 3)))
"Building Sequences"
(seq-concatenate
:eval (seq-concatenate 'vector '(1 2) '(c d)))
@ -897,13 +897,15 @@ A FUNC form can have any number of `:no-eval' (or `:no-value'),
:eval (seq-drop-while #'numberp '(1 2 c d 5)))
(seq-filter
:eval (seq-filter #'numberp '(a b 3 4 f 6)))
(seq-keep
:eval (seq-keep #'car-safe '((1 2) 3 t (a . b))))
(seq-remove
:eval (seq-remove #'numberp '(1 2 c d 5)))
(seq-remove-at-position
:eval (seq-remove-at-position '(a b c d e) 3)
:eval (seq-remove-at-position [a b c d e] 0))
(seq-group-by
:eval (seq-group-by #'cl-plusp '(-1 2 3 -4 -5 6)))
:eval (seq-group-by #'natnump '(-1 2 3 -4 -5 6)))
(seq-union
:eval (seq-union '(1 2 3) '(3 5)))
(seq-difference
@ -919,7 +921,7 @@ A FUNC form can have any number of `:no-eval' (or `:no-value'),
(seq-split
:eval (seq-split [0 1 2 3 5] 2))
(seq-take-while
:eval (seq-take-while #'cl-evenp [2 4 9 6 5]))
:eval (seq-take-while #'integerp [1 2 3.0 4]))
(seq-uniq
:eval (seq-uniq '(a b d b a c))))
@ -1372,13 +1374,20 @@ If SAME-WINDOW, don't pop to a new window."
(unless (bobp)
(insert "\n"))
(insert (propertize
(concat (substitute-command-keys data) "\n\n")
(substitute-command-keys data)
'face 'shortdoc-heading
'shortdoc-section t
'outline-level 1))
(insert (propertize
"\n\n"
'face 'shortdoc-heading
'shortdoc-section t)))
;; There may be functions not yet defined in the data.
((fboundp (car data))
(when prev
(insert (make-separator-line)))
(insert (make-separator-line)
;; This helps with hidden outlines (bug#53981)
(propertize "\n" 'face '(:height 0))))
(setq prev t)
(shortdoc--display-function data))))
(cdr (assq group shortdoc--groups))))
@ -1395,7 +1404,7 @@ If SAME-WINDOW, don't pop to a new window."
(start-section (point))
arglist-start)
;; Function calling convention.
(insert (propertize "(" 'shortdoc-function function))
(insert (propertize "(" 'shortdoc-function function 'outline-level 2))
(if (plist-get data :no-manual)
(insert-text-button
(symbol-name function)
@ -1529,7 +1538,10 @@ Example:
(define-derived-mode shortdoc-mode special-mode "shortdoc"
"Mode for shortdoc."
:interactive nil)
:interactive nil
(setq-local outline-search-function #'outline-search-level
outline-level (lambda ()
(get-text-property (point) 'outline-level))))
(defun shortdoc--goto-section (arg sym &optional reverse)
(unless (natnump arg)

View file

@ -56,7 +56,7 @@
;; which includes a kind of tutorial to get started with SMIE:
;;
;; SMIE: Weakness is Power! Auto-indentation with incomplete information
;; Stefan Monnier, <Programming> Journal 2020, volumn 5, issue 1.
;; Stefan Monnier, <Programming> Journal 2020, volume 5, issue 1.
;; doi: 10.22152/programming-journal.org/2021/5/1
;; A good background to understand the development (especially the parts

View file

@ -322,6 +322,10 @@ as the new values of the bound variables in the recursive invocation."
;; Keeping a work buffer around is more efficient than creating a
;; new temporary buffer.
(with-current-buffer (get-buffer-create " *string-pixel-width*")
;; `display-line-numbers-mode' is enabled in internal buffers
;; that breaks width calculation, so need to disable (bug#59311)
(when (bound-and-true-p display-line-numbers-mode)
(display-line-numbers-mode -1))
(delete-region (point-min) (point-max))
(insert string)
(car (buffer-text-pixel-size nil nil t)))))

View file

@ -374,7 +374,7 @@ Optional arg POS is a buffer position where to look for a fake header;
defaults to `point-min'."
(overlays-at (or pos (point-min))))
(defun tabulated-list-revert (&rest ignored)
(defun tabulated-list-revert (&rest _ignored)
"The `revert-buffer-function' for `tabulated-list-mode'.
It runs `tabulated-list-revert-hook', then calls `tabulated-list-print'."
(interactive)

View file

@ -569,7 +569,7 @@ spreadsheet files with invalid formatting."
(signal 'singularity-error nil)) ;Shouldn't get here
(singularity-error (error "No error from %s?" x))
(error nil)))
;;Test quit-handling in ses-update-cells. Cant' use `eval' here.
;; Test quit-handling in ses-update-cells. Can't use `eval' here.
(let ((inhibit-quit t))
(setq quit-flag t)
(condition-case nil

View file

@ -208,8 +208,14 @@ and if a matching region is found, place point at the start of the region."
(goto-char end)
(setq ended t)))))
;; End this at the first place the property changes value.
(setq end (previous-single-property-change
(point) property nil (point-min)))
(setq end
(if (and (> (point) (point-min))
(text-property--match-p
value (get-text-property (1- (point)) property)
predicate))
(previous-single-property-change (point)
property nil (point-min))
(point)))
(goto-char end))
(make-prop-match :beginning end
:end (1+ start)

View file

@ -353,6 +353,11 @@ This also updates the displayed table."
(let* ((cache (vtable--cache table))
(inhibit-read-only t)
(keymap (get-text-property (point) 'keymap))
(ellipsis (if (vtable-ellipsis table)
(propertize (truncate-string-ellipsis)
'face (vtable-face table))
""))
(ellipsis-width (string-pixel-width ellipsis))
(elem (and after-object
(assq after-object (car cache))))
(line (cons object (vtable--compute-cached-line table object))))
@ -370,7 +375,8 @@ This also updates the displayed table."
;; FIXME: We have to adjust colors in lines below this if we
;; have :row-colors.
(vtable--insert-line table line 0
(nth 1 cache) (vtable--spacer table))
(nth 1 cache) (vtable--spacer table)
ellipsis ellipsis-width)
(add-text-properties start (point) (list 'keymap keymap
'vtable table)))
;; We may have inserted a non-numerical value into a previously
@ -516,7 +522,8 @@ This also updates the displayed table."
(if (> (nth 1 elem) (elt widths index))
(concat
(vtable--limit-string
pre-computed (- (elt widths index) ellipsis-width))
pre-computed (- (elt widths index)
(or ellipsis-width 0)))
ellipsis)
pre-computed))
;; Recompute widths.
@ -524,7 +531,8 @@ This also updates the displayed table."
(if (> (string-pixel-width value) (elt widths index))
(concat
(vtable--limit-string
value (- (elt widths index) ellipsis-width))
value (- (elt widths index)
(or ellipsis-width 0)))
ellipsis)
value))))
(start (point))