Giter Club home page Giter Club logo

el-patch's Introduction

el-patch

Future-proof your Emacs Lisp customizations!

Table of contents

TL;DR

How do I get it

From MELPA, using your package manager of choice. See Installation. Emacs 25 and later is supported, please submit an issue if you want el-patch to support Emacs 24.

What is it

Like the advice system, el-patch provides a way to customize the behavior of Emacs Lisp functions that do not provide enough variables and hooks to let you make them do what you want. The advantage of using el-patch is that you will be notified if the definition of a function you are customizing changes, so that you are aware your customizations might need to be updated.

Using the same mechanism, el-patch also provides a way to make lazy-loading packages much more easy, powerful, and robust.

Installation

el-patch is available on MELPA. It is easiest to install it using straight.el:

(straight-use-package 'el-patch)

However, you may install using any other package manager if you prefer.

Why does it exist

Emacs provides a comprehensive set of customizable variables and hooks as well as a powerful advice system. Sometimes, however, these are not enough and you must override an entire function in order to change a detail of its implementation.

Such a situation is not ideal, since the original definition of the function might change when you update Emacs or one of its packages, and your overridden version would then be outdated. This could prevent you from benefitting from bugfixes made to the original function, or introduce new bugs into your configuration. Even worse, there is no way to tell when the original definition has changed! The correctness of your configuration is basically based on faith.

el-patch introduces another way to override Emacs Lisp functions. You can provide a patch which simultaneously specifies both the original and modified definitions of the function. When Emacs starts up, your patches act just like you had overridden the functions they are modifying. However, you can later ask el-patch to validate your patches—that is, to make sure that the original function definitions have not changed since you created the patches. If they have, el-patch will show you the difference using Ediff.

Of course, in an ideal world, el-patch would not be necessary, because user options and hooks could be made configurable enough to satisfy everyone's needs. Unfortunately, that will never truly be possible (or, arguably, desirable), so—like the advice system—el-patch offers a concession to the practical needs of your Emacs configuration.

Basic usage

Consider the following function defined in the company-statistics package:

(defun company-statistics--load ()
  "Restore statistics."
  (load company-statistics-file 'noerror nil 'nosuffix))

Suppose we want to change the third argument from nil to 'nomessage, to suppress the message that is logged when company-statistics loads its statistics file. We can do that by placing the following code in our init.el:

(el-patch-feature company-statistics)
(with-eval-after-load 'company-statistics
  (el-patch-defun company-statistics--load ()
    "Restore statistics."
    (load company-statistics-file 'noerror
          (el-patch-swap nil 'nomessage)
          'nosuffix)))

Simply calling el-patch-defun instead of defun defines a no-op patch: that is, it has no effect (well, not quite—see later). However, by including patch directives, you can make the modified version of the function different from the original.

In this case, we use the el-patch-swap directive. The el-patch-swap form is replaced with nil in the original definition (that is, the version that is compared against the "official" definition in company-statistics.el), and with 'nomessage in the modified definition (that is, the version that is actually evaluated in your init-file).

Note that it is important to cause the patch to be loaded after company-statistics is loaded. Otherwise, when company-statistics is loaded, the patch will be overwritten!

You may also be wondering what el-patch-feature does. The patch will still work without it; however, until company-statistics is actually loaded, el-patch will not be aware that you have defined the patch (since the code has not been run yet). Telling el-patch that you define a patch inside a with-eval-after-load for company-statistics allows M-x el-patch-validate-all to make sure to validate all your patches, and not just the ones currently defined. See also Validating patches that are not loaded yet.

Patch directives

  • (el-patch-add ARGS...)

    Insert forms. In the original definition, the entire form is removed, and in the modified definition, each of the ARGS is spliced into the surrounding form. For example, the following patch:

    (foo (el-patch-add bar baz) quux)
    

    resolves to this in the modified definition:

    (foo bar baz quux)
    
  • (el-patch-remove ARGS...)

    Remove forms. This is just like el-patch-add, except that the roles of the original and modified definitions are exchanged.

  • (el-patch-swap OLD NEW)

    Replace one form with another. In the original definition, the entire form is replaced with OLD, and in the modified definition, the entire form is replaced with NEW.

  • (el-patch-wrap [TRIML [TRIMR]] ARGS...)

    Wrap forms in a list, optionally prepending or postpending additional forms. This is the most complicated directive, so an example will probably be helpful. The following patch:

    (el-patch-wrap 1 1
      (or
       (eq (get-text-property (point) 'face) 'font-lock-doc-face)
       (eq (get-text-property (point) 'face) 'font-lock-string-face)))
    

    resolves to this in the original definition:

    (eq (get-text-property (point) 'face) 'font-lock-doc-face)
    

    and this in the modified definition:

    (or
     (eq (get-text-property (point) 'face) 'font-lock-doc-face)
     (eq (get-text-property (point) 'face) 'font-lock-string-face)))
    

    That is, the original eq call has been wrapped in an additional list, and also it has had forms inserted before and after it. The first 1 in the call to el-patch-wrap is the number of forms to insert before it, and the second 1 is the number of forms to insert after it.

    What you provide to el-patch-wrap for ARGS is the fully wrapped form, so you can think of TRIML and TRIMR as the number of forms to trim from each end of the ARGS before removing the surrounding parentheses.

    You can omit both TRIML and TRIMR; each defaults to zero. Notice that ARGS is always a list, so the number of arguments is either one, two, or three—thus eliminating any ambiguity about which argument is which.

  • (el-patch-splice [TRIML [TRIMR]] ARGS...)

    Splice forms into their containing form, optionally removing some from the beginning and end first. This is just like el-patch-wrap, except that the roles of the original and modified definitions are exchanged.

  • (el-patch-let VARLIST ARGS...)

    Sometimes you need to restructure a form in an inconvenient way. For example, suppose you need to turn the following form:

    (if $cond
        $then
      $else)
    

    into the following form:

    (cond
     ($cond $then)
     ($new-cond $new-then)
     (t $else))
    

    where $cond, $then, $new-cond, $new-then, and $else are all long forms with many sub-expressions. You could do it in the following way:

    (el-patch-swap
      (if $cond
          $then
        $else)
      (cond
       ($cond $then)
       ($new-cond $new-then)
       (t $else)))
    

    However, this is not ideal because you have repeated the forms and violated DRY.

    You could achieve the patch without any repetition by using the basic patch directives, but that would be hard to read. Wouldn't it be great if you could just do the following?

    (el-patch-let (($cond (... long form ...))
                   ($then (... another form ...))
                   ($else (... more code ...))
                   ($new-cond (... even more ...))
                   ($new-then (... lots more code ...)))
      (el-patch-swap
        (if $cond
            $then
          $else)
        (cond
         ($cond $then)
         ($new-cond $new-then)
         (t $else))))
    

    Well, you can. Welcome to el-patch.

  • (el-patch-literal ARGS...)

    Hopefully this will never happen, but you might need to use el-patch to modify functions that use symbols like el-patch-add. In this case, you can wrap a form in el-patch-literal to prevent anything within from being interpreted by el-patch. For example, the following form:

    (foo (el-patch-literal (el-patch-add bar baz)) quux)
    

    will be replaced with:

    (foo (el-patch-add bar baz) quux)
    

    in both the original and modified definitions. Thus, you can happily write el-patches that patch other el-patch definitions :)

  • (el-patch-concat ARGS...)

    This patch directive lets you concatenate strings. It is useful for modifying long string literals. For example, let's say that you have a string

    "Pretend this is a very long string we only want to write once"
    

    in a function you are patching. To change just a small part of this string, you could use el-patch-swap directly:

    (el-patch-swap
      "Pretend this is a very long string we only want to write once"
      "Pretend this is a really long string we only want to write once")
    

    But this repeats the rest of the string, violating DRY. Imagine if you just want to add a sentence to a 40-line docstring! Here's an alternative:

    (el-patch-concat
      "Pretend this is a "
      (el-patch-swap "very" "really")
      " long string we only want to write once")
    

    Basically, el-patch-concat just resolves all of its arguments, which may contain arbitrary patch directives, and then concatenates them as strings and splices the result into both the original and modified definition.

Defining patches

To patch a function, start by copying its definition into your init-file, and replace defun with el-patch-defun. Then modify the body of the function to use patch directives, so that the modified definition is what you desire.

You can also patch other types of definitions using:

  • el-patch-defmacro
  • el-patch-defsubst
  • el-patch-defvar
  • el-patch-defconst
  • el-patch-defcustom
  • el-patch-define-minor-mode

Some warnings:

  • Patching defmacro, defsubst, and defconst forms will not affect usages of them in already-defined functions, due to macroexpansion and byte-compilation. You may need to define no-op patches of client functions to get your changes to show up. Or take a different strategy—figuring out the best way to make a particular change to an internal function is often a complex process. You may also consider using advice, dynamic binding, and just plain forking the package.

  • Patching defvar, defconst, and defcustom forms will not affect the value of the variable, if it has already been defined. Thus, they are only useful for lazy-loading by default. To override this behavior and force the patches to reset the value of the variable, even if it is already defined, set el-patch-use-aggressive-defvar.

You can patch any definition form, not just those above. To register your own definition types, use the el-patch-deftype macro. For example, the el-patch-defun function is defined as follows:

(el-patch-deftype defun
  :classify el-patch-classify-function
  :locate el-patch-locate-function
  :font-lock el-patch-fontify-as-defun
  :declare ((doc-string 3)
            (indent defun)))

See the docstrings on the macro el-patch-deftype and the variable el-patch-deftype-alist for more detailed information. See also the source code of el-patch for examples of how to use el-patch-deftype.

Defining forks

Sometimes you want to define a slightly modified version of a function, so that you can use the patched version in your own code but you can still use the original version under its original name. This is easy to do:

(el-patch-defun (el-patch-swap my-old-fn my-new-fn) ...)

Be sure to include patch directives in the function body showing how your modified function is derived from the original, just like in any other patch.

Inspecting patches

You can run Ediff on a patch (original vs. modified definitions) by running M-x el-patch-ediff-patch and selecting the desired patch. Note that in this context, the "original" definition is the one specified by the patch, not the actual definition that is checked when you validate the patch (see below).

Validating patches

To validate a patch, run M-x el-patch-validate and select the desired patch. A warning will be printed if there is a difference between what the patch definition asserts the original definition of the function is and the actual original definition of the function.

If there is a difference, you can visualize it using Ediff with M-x el-patch-ediff-conflict.

You can validate all the patches that have been defined so far using M-x el-patch-validate-all.

Assuming you are byte-compiling your init-file, you can set el-patch-validate-during-compile to non-nil to validate patches when they are byte-compiled. There is no option to validate patches at runtime during startup because this makes startup incredibly slow. However, you could manually run el-patch-validate-all if such behavior is truly desired.

Removing patches

Use M-x el-patch-unpatch. Note that this does not technically remove the patch: instead, it sets the function or variable definition to the "original" definition as specified by the patch. These two actions will, however, be equivalent as long as the patch is not outdated (i.e., it is validated without errors by M-x el-patch-validate).

Lazy-loading packages

el-patch does not mind if you patch a function that is not yet defined. You can therefore use el-patch to help lazy-load a package.

As an example, consider the Ivy package. Ivy provides a minor mode called ivy-mode that sets completing-read-function to ivy-completing-read. The idea is that you call this function immediately, so that when a completing-read happens, it calls into the Ivy code.

Now, ivy-completing-read is autoloaded. So Ivy does not need to be loaded immediately: as soon as ivy-completing-read is called, Ivy will be loaded automatically. However, calling ivy-mode will trigger the autoload for Ivy, so we can't do that if we want to lazy-load the package. The natural thing to do is to copy the definition of ivy-mode into our init-file, but what if the original definition changes? That's where el-patch comes in. The code from Ivy looks like this:

(defvar ivy-mode-map
  (let ((map (make-sparse-keymap)))
    (define-key map [remap switch-to-buffer]
      'ivy-switch-buffer)
    (define-key map [remap switch-to-buffer-other-window]
      'ivy-switch-buffer-other-window)
    map)
  "Keymap for `ivy-mode'.")

(define-minor-mode ivy-mode
  "Toggle Ivy mode on or off.
Turn Ivy mode on if ARG is positive, off otherwise.
Turning on Ivy mode sets `completing-read-function' to
`ivy-completing-read'.

Global bindings:
\\{ivy-mode-map}

Minibuffer bindings:
\\{ivy-minibuffer-map}"
  :group 'ivy
  :global t
  :keymap ivy-mode-map
  :lighter " ivy"
  (if ivy-mode
      (progn
        (setq completing-read-function 'ivy-completing-read)
        (when ivy-do-completion-in-region
          (setq completion-in-region-function 'ivy-completion-in-region)))
    (setq completing-read-function 'completing-read-default)
    (setq completion-in-region-function 'completion--in-region)))

To enable ivy-mode while still lazy-loading Ivy, simply copy those definitions to your init-file before the call to ivy-mode, replacing defvar with el-patch-defvar and replacing define-minor-mode with el-patch-define-minor-mode. That is:

(el-patch-defvar ivy-mode-map
  (let ((map (make-sparse-keymap)))
    (define-key map [remap switch-to-buffer]
      'ivy-switch-buffer)
    (define-key map [remap switch-to-buffer-other-window]
      'ivy-switch-buffer-other-window)
    map)
  "Keymap for `ivy-mode'.")

(el-patch-define-minor-mode ivy-mode
  "Toggle Ivy mode on or off.
Turn Ivy mode on if ARG is positive, off otherwise.
Turning on Ivy mode sets `completing-read-function' to
`ivy-completing-read'.

Global bindings:
\\{ivy-mode-map}

Minibuffer bindings:
\\{ivy-minibuffer-map}"
  :group 'ivy
  :global t
  :keymap ivy-mode-map
  :lighter " ivy"
  (if ivy-mode
      (progn
        (setq completing-read-function 'ivy-completing-read)
        (when ivy-do-completion-in-region
          (setq completion-in-region-function 'ivy-completion-in-region)))
    (setq completing-read-function 'completing-read-default)
    (setq completion-in-region-function 'completion--in-region)))

(ivy-mode 1)

(featurep 'ivy) ;; => ivy is still not loaded!

It's really that simple!

Validating patches that are not loaded yet

If you want to define a patch for a function provided by an unloaded feature, it is likely that you will just put the patch in a with-eval-after-load for the feature. But then el-patch-validate and el-patch-validate-all will not be able to validate your patch, because it is not yet defined.

To get around this problem, you can add functions to el-patch-pre-validate-hook in order to make sure all your patches are defined (for instance, you might need to require some features or even enable a custom minor mode). This hook is run before el-patch-validate-all, and also before el-patch-validate when you provide a prefix argument.

Since defining some patches after a feature is loaded is such a common operation, el-patch provides a convenience macro for it: el-patch-feature. You can call this macro with an (unquoted) feature name, and it will create a function that loads that feature, and add it to el-patch-pre-validate-hook for you.

If you don't want all of your patches to be defined all the time, you can put some functions in el-patch-post-validate-hook to disable them again. For some examples of how to use these hooks, check out Radian Emacs.

Integration with use-package

You can enable the use-package integration of el-patch by toggling the global minor mode el-patch-use-package-mode, but it is more convenient to set the variable el-patch-enable-use-package-integration (defaults to non-nil) and then the mode will be toggled appropriately once el-patch and use-package have both been loaded.

The use-package integration defines two new use-package keywords, :init/el-patch and :config/el-patch. They are analogous to :init and :config, but each top-level form is converted into an el-patch form: for example, a defun will be turned into an el-patch-defun, and so on. (Definition forms that have no corresponding el-patch macro are left as is.) The resulting code is prepended to the code in :init or :config, respectively. Also, if you provide either keyword, then a call to el-patch-feature is inserted into the :init section.

Templates

In some cases, you may want to patch one or two forms in a long definition of a function or a macro. Defining the patch would still require copying all unpatched forms and updating the patch when these forms change. For these cases, it would be better if we can simply search for the forms that we want to patch in the original definition and patch only those. Enter el-patch templates.

As an example, say we want to define a patch of restart-emacs so that it starts a new emacs instance without killing the current one. Instead of defining a patch that includes the complete definition of restart-emacs, we can define a template as follows

(el-patch-define-template
  (defun (el-patch-swap restart-emacs radian-new-emacs))
  (el-patch-concat
    (el-patch-swap
      "Restart Emacs."
      "Start a new Emacs session without killing the current one.")
    ...
    (el-patch-swap "restarted" "started")
    ...
    (el-patch-swap "restarted" "started")
    ...
    (el-patch-swap "restarted" "started")
    ...)
  (restart-args ...)
  (el-patch-remove (kill-emacs-hook ...))
  (el-patch-swap
    (save-buffers-kill-emacs)
    (restart-emacs--launch-other-emacs restart-args)))

The first argument is a list that comprises the type, defun in this case, and the name of the object that we are patching. Using an el-patch-swap here allows us to define a fork, radian-new-emacs. Had we wanted to simply patch the function we would pass (defun restart-emacs) as the first argument. Every other argument defines a template for a patch. To build the final patch, every argument is resolved to figure out the original form which is then matched against all forms in the original definition of the object and, if uniquely found, the patch is spliced in its place. The special form ... is used to match one or more forms or, if it is inside el-patch-concat as above, one or more characters in a string. Patch templates need not be, or even contain, el-patch-* directives. For example, the purpose of the argument (restart-args ...) is to make sure that such a form exists in the function definition without actually patching it.

After defining the template, you can run the interactive command el-patch-insert-template to insert the patch definition in the current buffer based on the defined template. Alternatively, you may use the command el-patch-eval-template which directly evaluates the patch. The function el-patch-define-and-eval-template defines and evaluates a template in one go. It is recommended that you compile your init-file if you use el-patch-define-and-eval-template to avoid the overhead of template matching when starting Emacs. el-patch will issue a warning if el-patch-define-and-eval-template is called at runtime and el-patch-warn-on-eval-template is non-nil (which is the default).

Templates assume that the original definition of the object is accessible, for example, using find-function-noselect for functions.

Like patches, templates can be validated using el-patch-validate-template and el-patch-validate-all-templates.

Patch variants

You can define multiple versions of the same patch. Normally, (re)defining a patch will just overwrite the old version entirely. However, if you dynamically bind el-patch-variant to a different (symbol) value for each call, then the latter patch is still the one that takes effect, but el-patch retains a record of both patches, meaning they can be inspected and validated individually. See #29.

You may also define patches of functions as :override advices instead of overriding the original definition. This is done by setting el-patch-use-advice to a non-nil value (either dynamically around a patch or globally). The patched function must have the same name and number of arguments as the original function.

Usage with byte-compiled init-file

el-patch does not need to be loaded at runtime just to define patches. This means that if you byte-compile your init-file, then el-patch will not be loaded when you load the compiled code.

For this to work, you will need to stick to defining patches with el-patch-def* and declaring features with el-patch-feature. Anything else will cause el-patch to be loaded at runtime.

If you do not byte-compile your init-file, then all of this is immaterial.

But how does it work?

Magic.

But how does it actually work?

The basic idea is simple. When a patch is defined, the patch definition is resolved to figure out the modified definition is. Then that definition is installed by evaluating it (by using el-patch--stealthy-eval, so that looking up the function definition will return the original location rather than the el-patch invocation location, and also using makunbound to override a previous variable definition if el-patch-use-aggressive-defvar is non-nil).

The patch definition is also recorded in the hash el-patch--patches. This allows for the functionality of M-x el-patch-ediff-patch. Obtaining the actual original definition of a function is done using a modified version of find-function-noselect, which provides for M-x el-patch-validate and M-x el-patch-ediff-conflict.

When you call M-x el-patch-unpatch, the patch definition is resolved again and the original version is installed by evaluating it.

But does it actually work?

It doesn't seem to crash my Emacs, at least.

Contributor guide

Please see the contributor guide for my projects.

el-patch's People

Contributors

agsdot avatar caadar avatar fuco1 avatar haji-ali avatar jellelicht avatar pythonnut avatar raxod502 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

el-patch's Issues

Patching cl-defun

Hi,

first of all thanks for this package, it seems to be exactly the piece of the puzzle I need for a minor "problem" I have.

I'd like to patch this function:

(cl-defun psession--restore-objects-from-directory
    (&optional (dir psession-elisp-objects-default-directory))
  (let ((file-list (directory-files dir t directory-files-no-dot-files-regexp)))
    (cl-loop for file in file-list do (and file (load file)))))

My aim is to replace (load file) with (load file nil 'nomessage nil). This is what I did:

(el-patch-feature psession)
(with-eval-after-load 'psession
  (el-patch-defun psession--restore-objects-from-directory
    (&optional (dir psession-elisp-objects-default-directory))
    (let ((file-list (directory-files dir t directory-files-no-dot-files-regexp)))
      (cl-loop for file in file-list do
               (and file
                    (load file nil (el-patch-swap nil 'nomessage) nil))))))

In the :init part of my use-package psession snippet. However, upon restarting Emacs I get:

Eager macro-expansion failure: (error "Malformed arglist: (&optional (dir psession-elisp-objects-default-directory))")

What am I doing wrong? :)

el-patch-validate-all fails on first missing file

I have many patches that I would like to check with el-patch-validate-all. However, some patches have missing files. At the moment, the validation stops whenever any patch is missing a file with an error find-library-name: Can’t find library....

I would have instead expected the validation to continue to other patches and a full report to be printed at the end.

Add use-package extension

I have a lot of use-package forms that start with an el-patch-feature declaration, then patch a bunch of functions/variables to lazy-load the package. It would be nice if there were a :patch (or maybe :patch-init and :patch-config) keyword where I could just paste the function definitions and defun would automatically turn into el-patch-defun, etc.

Swapped arguments in patch validation?

I define a patch like this:

(defun foo (x)
  (+ x 1))
(el-patch-defun foo (x)
  (+ x (el-patch-swap 1 2)))

The patch works fine, but when I run M-x el-patch-validate on the patch, I receive the error "el-patch--classify-definition-type: Unexpected definition type foo". It seems that the function name and definition type are swapped somewhere.

BTW, el-patch is a great tool. I thought about writing a tool with a similar purpose and I'm glad it's already done :-). Thank you!

Allow "forking" of functions.

Sometimes I want to base my own slightly modified function on an existing function, either built-in or from a different package.

I would still like to be notified in case of updates to the original but I do not want to replace the original: I will use my own version on my own and the original package will use its own definition.

An example I have is the function try-expand-all-abbrevs. I want to modify it to only use the local abbrev table. The change is only on one line where I reference the "abbrev table". So I will use this but other code might still want to use the original. Right now I have a copy of the function with the change applied in my config.

Finding source after compiling patch

I am running into a weird issue for some of my patches (but strangely not all of them).

I have some patches which I put in a file and compile it. If I don't require the compiled file I am able to see the original function and get its source (through find-function-noselect). However, if I put the require in my init file and try to validate the patch, el-patch complains that it cannot find the source.

Indeed, find-function-noselect cannot locate the source because, it seems, the function is assumed to be defined in my compiled source file whose source is unavailable (in my case the reason is because the patch is defined through a template).

Is this expected?

Does el-patch byte compile the function that it patches?

Sorry if this question is silly, but I'm trying to using el-patch to patch a function for performance reasons. In this case, it seems like it might be important that the patched function is also byte compiled. Does el-patch guarantee this?

Support arbitrary source-location functions

The fact that source location lookups for custom patch types was completely broken was pointed out in #27. However, that pull request doesn't make it fully generic; we should add a new :locate keyword (or similar) which allows you to give a function that's used to locate the definition source code, and use that in el-patch--find-symbol.

Use docstring to document patch

Currently, I have 2 issues with regards to docstring, and this proposal
addresses both of them.

  1. Some functions have a giant block of text for docstring and I'm required to
    repeat this docstring in my patch. For example, el-patch thinks the patch
    below has incorrect expected definition.

    (defun foo ()
      "Five
    paragraphs
    of
    information."
      1)
    
    (el-patch-defun foo ()
      (el-patch-swap 1 2))

    I think you just shouldn't need to repeat the docstring in general since it
    doesn't affect value/behavior.

  2. When writing a patch, I usually want to note down what did I patch and why.
    For now, I just have to add a comment next to the patch defition.

Patch with repurposed docstring will look like this

(el-patch-defun foo ()
  "Replace 1 with 2 because reasons."
  (el-patch-swap 1 2))

The rewritten docstring for can show this as well

(documentation 'foo)
"Five
paragraphs
of
information.

[This function was patched by ‘el-patch’. Reason: \"Replace 1 with 2 because reasons.\"]"

Patch function in a compiled init.el

I am using Doom Emacs, which byte-compiles its configuration file. For functions belong to a package, I managed to patch them as instructed with el-patch-feature followed by el-patch-defun wrapped in with-eval-after-load. However, some functions do not belong to a package, they are defined by Doom. How can I patch those functions? I tried to put the el-patch-defun in some with-eval-after-load macro whose related package should be loaded before the function patch is run, which does not work. Currently I am using a workaround which advice before the function to patch. In the advice, it deactivate the patch (so it runs only once), and call el-patch-defun.

Avoid el-patch-pre-validate-hook boilerplate

This is a very common pattern (repeated nine times in Radian, for example):

(defun radian--enable-ivy-patches ()
  (require 'ivy))
(add-hook 'el-patch-pre-validate-hook #'radian--enable-ivy-patches)

We should provide a way to automate it.

void-function: el-patch-fontify-as-defun

I'm using Doom Emacs, which performs a lot of pre-compilation of your config into autoload files.

Anyway, it appears that the latest release, which added el-patch-fontify-as-defun, breaks my config.

Debugger entered--Lisp error: (void-function el-patch-fontify-as-defun)
  el-patch-fontify-as-defun(el-patch-cl-defun)
  byte-code("\301\302\303\304#\305\306!\210\307 \210\310\300!\203\26\0\10\204\30\0\311\20\312\10\236\313\314!\1\203*\0\1\1\241\266\3\2025\0\312\1B\211\262\3\10B..." [el-patch-deftype-alist function-put el-patch-deftype lisp-indent-function defun require el-patch-stub el-patch--deftype-stub-setup boundp nil cl-defun copy-tree (:classify el-patch-classify-function :locate el-patch-locate-function :font-lock el-patch-fontify-as-defun :declare ((doc-string 3) (indent defun))) el-patch-fontify-as-defun el-patch-cl-defun] 5)
  load("/Users/rmirelan/.doom.d/doom.emacs.d/.local/autoloads.29.0.50" nil nomessage)
  (condition-case e (load (string-remove-suffix ".el" doom-autoloads-file) nil 'nomessage) ((debug file-missing) (if (locate-file doom-autoloads-file load-path) (signal 'doom-autoload-error e) (signal 'doom-error (list "Doom is in an incomplete state" "run 'doom sync' on the command line to repair it")))))
  (progn (setq doom-init-p t) (if doom-debug-p (progn (let ((inhibit-message (active-minibuffer-window))) (message #("DOOM Initializing Doom" 0 5 (face font-lock-comment-face)))))) (let ((--dolist-tail-- '(exec-path load-path))) (while --dolist-tail-- (let ((var (car --dolist-tail--))) (set-default var (get var 'initial-value)) (setq --dolist-tail-- (cdr --dolist-tail--))))) (condition-case e (load (string-remove-suffix ".el" doom-autoloads-file) nil 'nomessage) ((debug file-missing) (if (locate-file doom-autoloads-file load-path) (signal 'doom-autoload-error e) (signal 'doom-error (list "Doom is in an incomplete state" "run 'doom sync' on the command line to repair it"))))) (if doom-debug-p (doom-debug-mode 1)) (if (and (or (display-graphic-p) (daemonp)) doom-env-file) (progn (progn (set-default 'process-environment (get 'process-environment 'initial-value))) (doom-load-envvars-file doom-env-file 'noerror))) (require 'core-modules) (autoload 'doom-initialize-packages "core-packages") (eval-after-load 'package #'(lambda nil (require 'core-packages))) (eval-after-load 'straight #'(lambda nil (doom-initialize-packages))) (if noninteractive nil (add-hook 'after-change-major-mode-hook #'doom-run-local-var-hooks-maybe-h 100) (add-hook 'hack-local-variables-hook #'doom-run-local-var-hooks-h) (add-hook 'emacs-startup-hook #'doom-load-packages-incrementally-h) (add-hook 'window-setup-hook #'doom-display-benchmark-h) (doom-run-hook-on 'doom-first-buffer-hook '(find-file-hook doom-switch-buffer-hook)) (doom-run-hook-on 'doom-first-file-hook '(find-file-hook dired-initial-position-hook)) (doom-run-hook-on 'doom-first-input-hook '(pre-command-hook)) (add-hook 'doom-first-buffer-hook #'gcmh-mode)))
  (if (or force-p (not doom-init-p)) (progn (setq doom-init-p t) (if doom-debug-p (progn (let ((inhibit-message (active-minibuffer-window))) (message #("DOOM Initializing Doom" 0 5 (face font-lock-comment-face)))))) (let ((--dolist-tail-- '(exec-path load-path))) (while --dolist-tail-- (let ((var (car --dolist-tail--))) (set-default var (get var 'initial-value)) (setq --dolist-tail-- (cdr --dolist-tail--))))) (condition-case e (load (string-remove-suffix ".el" doom-autoloads-file) nil 'nomessage) ((debug file-missing) (if (locate-file doom-autoloads-file load-path) (signal 'doom-autoload-error e) (signal 'doom-error (list "Doom is in an incomplete state" "run 'doom sync' on the command line to repair it"))))) (if doom-debug-p (doom-debug-mode 1)) (if (and (or (display-graphic-p) (daemonp)) doom-env-file) (progn (progn (set-default 'process-environment (get 'process-environment 'initial-value))) (doom-load-envvars-file doom-env-file 'noerror))) (require 'core-modules) (autoload 'doom-initialize-packages "core-packages") (eval-after-load 'package #'(lambda nil (require 'core-packages))) (eval-after-load 'straight #'(lambda nil (doom-initialize-packages))) (if noninteractive nil (add-hook 'after-change-major-mode-hook #'doom-run-local-var-hooks-maybe-h 100) (add-hook 'hack-local-variables-hook #'doom-run-local-var-hooks-h) (add-hook 'emacs-startup-hook #'doom-load-packages-incrementally-h) (add-hook 'window-setup-hook #'doom-display-benchmark-h) (doom-run-hook-on 'doom-first-buffer-hook '(find-file-hook doom-switch-buffer-hook)) (doom-run-hook-on 'doom-first-file-hook '(find-file-hook dired-initial-position-hook)) (doom-run-hook-on 'doom-first-input-hook '(pre-command-hook)) (add-hook 'doom-first-buffer-hook #'gcmh-mode))))
  doom-initialize()
  load-with-code-conversion("/Users/rmirelan/.doom.d/doom.emacs.d/init.el" "/Users/rmirelan/.doom.d/doom.emacs.d/init.el" nil nil)
  load("/Users/rmirelan/.doom.d/doom.emacs.d/init.el")
  (let* ((emacs-directory (file-name-as-directory (chemacs-emacs-profile-key 'user-emacs-directory))) (init-file (expand-file-name "init.el" emacs-directory)) (custom-file- (chemacs-emacs-profile-key 'custom-file init-file)) (server-name- (chemacs-emacs-profile-key 'server-name))) (setq user-emacs-directory emacs-directory) (if server-name- (progn (setq server-name server-name-))) (mapcar #'(lambda (env) (setenv (car env) (cdr env))) (chemacs-emacs-profile-key 'env)) (if (chemacs-emacs-profile-key 'straight-p) (progn (chemacs-load-straight))) (load init-file) (if (not custom-file) (progn (setq custom-file custom-file-) (if (equal custom-file init-file) nil (load custom-file)))))
  chemacs-load-profile("default")
  (if args (let ((s (split-string (car args) "="))) (cond ((equal (car args) "--with-profile") (add-to-list 'command-switch-alist '("--with-profile" lambda (_) (pop command-line-args-left))) (chemacs-load-profile (car (cdr args)))) ((equal (car s) "--with-profile") (add-to-list 'command-switch-alist (cons (car args) '(lambda ...))) (chemacs-load-profile (mapconcat 'identity (cdr s) "="))) (t (chemacs-check-command-line-args (cdr args))))) (chemacs-load-profile (chemacs-detect-default-profile)))
  chemacs-check-command-line-args(nil)
  (cond ((equal (car args) "--with-profile") (add-to-list 'command-switch-alist '("--with-profile" lambda (_) (pop command-line-args-left))) (chemacs-load-profile (car (cdr args)))) ((equal (car s) "--with-profile") (add-to-list 'command-switch-alist (cons (car args) '(lambda (_)))) (chemacs-load-profile (mapconcat 'identity (cdr s) "="))) (t (chemacs-check-command-line-args (cdr args))))
  (let ((s (split-string (car args) "="))) (cond ((equal (car args) "--with-profile") (add-to-list 'command-switch-alist '("--with-profile" lambda (_) (pop command-line-args-left))) (chemacs-load-profile (car (cdr args)))) ((equal (car s) "--with-profile") (add-to-list 'command-switch-alist (cons (car args) '(lambda (_)))) (chemacs-load-profile (mapconcat 'identity (cdr s) "="))) (t (chemacs-check-command-line-args (cdr args)))))
  (if args (let ((s (split-string (car args) "="))) (cond ((equal (car args) "--with-profile") (add-to-list 'command-switch-alist '("--with-profile" lambda (_) (pop command-line-args-left))) (chemacs-load-profile (car (cdr args)))) ((equal (car s) "--with-profile") (add-to-list 'command-switch-alist (cons (car args) '(lambda ...))) (chemacs-load-profile (mapconcat 'identity (cdr s) "="))) (t (chemacs-check-command-line-args (cdr args))))) (chemacs-load-profile (chemacs-detect-default-profile)))
  chemacs-check-command-line-args(("/Applications/MacPorts/Emacs.app/Contents/MacOS/Em..."))
  load-with-code-conversion("/Users/rmirelan/.emacs" "/Users/rmirelan/.emacs" t t)
  load("~/.emacs" noerror nomessage)
  startup--load-user-init-file(#f(compiled-function () #<bytecode -0x1492e7863b814e7>) #f(compiled-function () #<bytecode -0x1f3c6eaddc0e4835>) t)
  command-line()
  normal-top-level()

el-patch validates against macro-expanded version of function

Consider the following patch:

(el-patch-feature org-roam)
(with-eval-after-load 'org-roam
  (el-patch-defun org-roam-buffer-persistent-redisplay ()
    "Recompute contents of the persistent `org-roam-buffer'.
Has no effect when there's no `org-roam-node-at-point'."
    (when-let ((node (org-roam-node-at-point)))
      (unless (equal node org-roam-buffer-current-node)
        (setq org-roam-buffer-current-node node
              org-roam-buffer-current-directory org-roam-directory)
        (el-patch-remove (with-current-buffer (get-buffer-create org-roam-buffer)
                           (org-roam-buffer-render-contents)
                           (add-hook 'kill-buffer-hook #'org-roam-buffer--persistent-cleanup-h nil t)))
        (el-patch-add
          (if (get-buffer-window org-roam-buffer)
	      (delete-window (get-buffer-window org-roam-buffer)))
          (let ((buffer (get-buffer-create org-roam-buffer)))
	    (with-current-buffer buffer
	      (org-roam-buffer-render-contents)
	      (add-hook 'kill-buffer-hook #'org-roam-buffer--persistent-cleanup-h nil t))
	    (display-buffer buffer)))))))

Applying it at startup and validating the patch yields the following diff (in which macros are expanded in the expected buffer):

image

However, when commenting out the patch during startup and manually eval the el-patch-defun after startup completed, validation succeeds and the diff does not contain expanded macros:

image

Could the problem be related to the emacs native compilation feature? Verified the issue persists after rebuilding emacs without native compilation support.

In GNU Emacs 29.0.50 (build 1, x86_64-pc-linux-gnu, GTK+ Version 3.24.29, cairo version 1.16.0)
 of 2021-11-05 built on leonard-xps13
Repository revision: 37632dae180e9d5196ef01343fefa9234c5f05b9
Repository branch: feature/pgtk
Repository: https://github.com/leezu/emacs
System Description: Gentoo/Linux

Configured using:
 'configure --with-cairo --with-x-toolkit=no --with-pgtk --with-modules --with-native-compilation'

Configured features:
ACL CAIRO DBUS FREETYPE GIF GLIB GMP GNUTLS GPM GSETTINGS HARFBUZZ JPEG
JSON LCMS2 LIBOTF LIBSYSTEMD LIBXML2 MODULES NATIVE_COMP NOTIFY INOTIFY
PDUMPER PGTK PNG RSVG SECCOMP SOUND THREADS TIFF TOOLKIT_SCROLL_BARS XIM
GTK3 ZLIB

Important settings:
  value of $LC_COLLATE: C
  value of $LC_CTYPE: zh_CN.UTF-8
  value of $LANG: en_US.UTF-8
  locale-coding-system: utf-8-unix

How to patch vector literal given with backticks

Thanks for the great package, Radon!

I'm having a bit of trouble figuring out how to patch the following defconst found in flycheck.

(defconst flycheck-error-list-format
  `[("File" 6)
    ("Line" 5 flycheck-error-list-entry-< :right-align t)
    ("Col" 3 nil :right-align t)
    ("Level" 8 flycheck-error-list-entry-level-<)
    ("ID" 6 t)
    (,(flycheck-error-list-make-last-column "Message" 'Checker) 0 t)]
  "Table format for the error list.")

Here is a reproducible example

(use-package el-patch
  :demand t
  :straight t)

(use-package flycheck
  :straight t)

(el-patch-feature flycheck)
(with-eval-after-load 'flycheck
  (el-patch-defconst flycheck-error-list-format
    `[("File" 6)
      ("Line" 5 flycheck-error-list-entry-< :right-align t)
      ("Col" 3 nil :right-align t)
      ("Level" 8 flycheck-error-list-entry-level-<)
      ("ID" (el-patch-swap 6 20) t)
      (,(flycheck-error-list-make-last-column "Message" 'Checker) 0 t)]
    "Table format for the error list."))

Unfortunately, I just can't figure out the right combination of backticks to use to make the patch validate. I believe there is an incompatibility with nested backticks? For example,

(with-eval-after-load 'flycheck
  (el-patch-defconst flycheck-error-list-format
    `[("File" 6)
      ("Line" 5 flycheck-error-list-entry-< :right-align t)
      ("Col" 3 nil :right-align t)
      ("Level" 8 flycheck-error-list-entry-level-<)
      ("ID" ,(el-patch-swap 6 20) t)
      (,(flycheck-error-list-make-last-column "Message" 'Checker) 0 t)]
    "Table format for the error list."))

throws an error Debugger entered--Lisp error: (error "Can’t use ‘el-patch-swap’ outside of an ‘el-patch’"). Is there an actual bug or am I misunderstanding?

Eager macro-expansion failure: (wrong-number-of-arguments (2 . 2) 3)

Hello,

So I'm trying to use el-patch-defun to redefine the icomplete-completions function in order to make it look a little less ugly.

However, Emacs keeps throwing this error

Eager macro-expansion failure: (wrong-number-of-arguments (2 . 2) 3)

At first, I thought it was because the icomplete-completions function has to different parts in it's body...

The first

  (let* ((ignored-extension-re
          (and minibuffer-completing-file-name
               icomplete-with-completion-tables
               completion-ignored-extensions
               (concat "\\(?:\\`\\.\\./\\|"
                       (regexp-opt completion-ignored-extensions)
                       "\\)\\'")))
         (minibuffer-completion-table candidates))
    (minibuffer-completion-predicate
     (if ignored-extension-re
         (lambda (cand)
           (and (not (string-match ignored-extension-re cand))
                (or (null predicate)
                    (funcall predicate cand))))
       predicate))
    (md (completion--field-metadata (icomplete--field-beg)))
    (comps (icomplete--sorted-completions)
           (last (if (consp comps) (last comps)))
           (base-size (cdr last))
           (open-bracket (if require-match "(" "["))
           (close-bracket (if require-match ")" "]"))
           ;; `concat'/`mapconcat' is the slow part.
           (if (not (consp comps)))))

And then a progn

(progn ;;(debug (format "Candidates=%S field=%S" candidates name))
    (format " %sNo matches%s" open-bracket close-bracket
            (if last (setcdr last nil))
            (let* ((most-try
                    (if (and base-size (> base-size 0))
                        (completion-try-completion
                         name candidates predicate (length name) md)
                      ;; If the `comps' are 0-based, the result should be
                      ;; the same with `comps'.
                      (completion-try-completion
                       name comps nil (length name) md))))
              (most (if (consp most-try) (car most-try)
                      (if most-try (car comps) "")))
              ;; Compare name and most, so we can determine if name is
              ;; a prefix of most, or something else.
              (compare (compare-strings name nil nil)
                       most nil nil completion-ignore-case)
              (ellipsis (if (char-displayable-p ?…) "" "..."))
              (determ (unless (or (eq t compare) (eq t most-try)))
                      (= (setq compare (1- (abs compare)))
                         (length most)
                         (concat open-bracket
                                 (cond)))
                      ((= compare (length name))
                       ;; Typical case: name is a prefix.
                       (substring most compare))
                      ;; Don't bother truncating if it doesn't gain
                      ;; us at least 2 columns.
                      ((< compare (+ 2 (string-width ellipsis))) most)
                      (t (concat ellipsis (substring most compare)
                                 close-bracket)))
              ;;"-prospects" - more than one candidate
              (prospects-len (+ (string-width))
                             (or determ (concat open-bracket close-bracket)
                                 (string-width icomplete-separator)
                                 (+ 2 (string-width ellipsis)) ;; take {…} into account
                                 (string-width (buffer-string))
                                 (prospects-max
                                  ;; Max total length to use, including the minibuffer content.
                                  (* (+ icomplete-prospects-height
                                        ;; If the minibuffer content already uses up more than
                                        ;; one line, increase the allowable space accordingly.
                                        (/ prospects-len (window-width)))
                                     (window-width)))))
              ;; Find the common prefix among `comps'.
              ;; We can't use the optimization below because its assumptions
              ;; aren't always true, e.g. when completion-cycling (bug#10850):
              ;; (if (eq t (compare-strings (car comps) nil (length most)
              ;; 			 most nil nil completion-ignore-case))
              ;;     ;; Common case.
              ;;     (length most)
              ;; Else, use try-completion.
              (prefix (when icomplete-hide-common-prefix)
                      (try-completion "" comps
                                      (prefix-len))
                      (and (stringp prefix
                                    ;; Only hide the prefix if the corresponding info
                                    ;; is already displayed via `most'.
                                    (string-prefix-p prefix most t)
                                    (length prefix)))) ;;)
              prospects comp limit)))
  (if (or (eq most-try t) (not (consp (cdr comps))))
      (setq prospects nil)
    (when (member name comps)
      ;; NAME is complete but not unique.  This scenario poses
      ;; following UI issues:
      ;;
      ;; - When `icomplete-hide-common-prefix' is non-nil, NAME
      ;;   is stripped empty.  This would make the entry
      ;;   inconspicuous.
      ;;
      ;; - Due to sorting of completions, NAME may not be the
      ;;   first of the prospects and could be hidden deep in
      ;;   the displayed string.
      ;;
      ;; - Because of `icomplete-prospects-height' , NAME may
      ;;   not even be displayed to the user.
      ;;
      ;; To circumvent all the above problems, provide a visual
      ;; cue to the user via an "empty string" in the try
      ;; completion field.
      (setq determ (concat open-bracket "" close-bracket)))
    ;; Compute prospects for display.
    (while (and comps (not limit))
      (setq comp)
      (if prefix-len (substring (car comps) prefix-len) (car comps))
      comps (cdr comps)
      (setq prospects-len
            (+ (string-width comp))
            (string-width icomplete-separator)
            prospects-len)
      (if (< prospects-len prospects-max)))
    (push comp prospects
          (setq limit t)))
  (setq prospects (nreverse prospects))
  ;; Decorate first of the prospects.
  (when prospects
    (let ((first (copy-sequence (pop prospects))))
      (put-text-property 0 (length first)
                         'face 'icomplete-first-match first)
      (push first prospects
            ;; Restore the base-size info, since completion-all-sorted-completions
            ;; is cached.
            (if last (setcdr last base-size)))))
  (if prospects
      (concat determ
              "{"
              (mapconcat 'identity prospects icomplete-separator)
              (and limit (concat icomplete-separator ellipsis))
              "}")
    (concat determ " [Matched]"))

But that's not the case. I'm still getting the error that there's too many arguments when I place the large progn inside of the let*. Which doesn't make sense, because from what I can tell it's no different from examples provided in your Radian configuration.

Here's the plain icomplete-completions function. I'm not sure why I can find why el-patch-defun is throwing this error.

Fail patch at Emacs init but work fine if form evaluated by hand

Some time ago I modify helm-highlight-bookmark by el-patch and all was fine. Today I update Emacs packages including Helm and observe some nasty thing: this function work as before patching.

What I see:

  1. Restart Emacs, describe-function helm-highlight-bookmark => "PATCHED!.." It seems OK, but wait...

  2. Eval (helm-filtered-bookmarks). It's my command of interest. It works unexpectedly though. Let's look at function again!

  3. Describe-function helm-highlight-bookmark => Oops, I see vanilla docstring only. Where is my patch?

  4. Eval following form by hand. Now patch really applied and all works fine.

After playing with various Emacs rc combo without success I return to old `advice-add' technics. It work as expected with the same configs.

So, Emacs Messages without errors, if form evaluated by hand it work, patch validation is OK also, another patched functions work fine. But loading this func at Emacs init is unsufficient. Mystique thing...

(el-patch-defun helm-highlight-bookmark (bookmarks _source)
  (el-patch-swap
  "Used as `filtered-candidate-transformer' to colorize bookmarks."
	"PATCHED! Used as `filtered-candidate-transformer' to colorize bookmarks.

PATCH NOTE: AutoFS adoptation.")
  (let ((non-essential t))
    (cl-loop for i in bookmarks
          for isfile        = (bookmark-get-filename i)
          for hff           = (helm-bookmark-helm-find-files-p i)
          for handlerp      = (and (fboundp 'bookmark-get-handler)
                                   (bookmark-get-handler i))
          for isw3m         = (and (fboundp 'helm-bookmark-w3m-bookmark-p)
                                   (helm-bookmark-w3m-bookmark-p i))
          for isgnus        = (and (fboundp 'helm-bookmark-gnus-bookmark-p)
                                   (helm-bookmark-gnus-bookmark-p i))
          for isman         = (and (fboundp 'helm-bookmark-man-bookmark-p) ; Man
                                   (helm-bookmark-man-bookmark-p i))
          for iswoman       = (and (fboundp 'helm-bookmark-woman-bookmark-p) ; Woman
                                   (helm-bookmark-woman-bookmark-p i))
          for isannotation  = (bookmark-get-annotation i)
          for isabook       = (string= (bookmark-prop-get i 'type)
                                       "addressbook")
          for isinfo        = (eq handlerp 'Info-bookmark-jump)
          for loc = (bookmark-location i)
          for len =  (string-width i)
          for trunc = (if (and helm-bookmark-show-location
                               (> len bookmark-bmenu-file-column))
                          (helm-substring
                           i bookmark-bmenu-file-column)
                        i)
          ;; Add a * if bookmark have annotation
          if (and isannotation (not (string-equal isannotation "")))
          do (setq trunc (concat "*" (if helm-bookmark-show-location trunc i)))
          for sep = (and helm-bookmark-show-location
                         (make-string (- (+ bookmark-bmenu-file-column 2)
                                         (string-width trunc))
                                      ? ))
          for bmk = (cond ( ;; info buffers
                           isinfo
                           (propertize trunc 'face 'helm-bookmark-info
                                       'help-echo isfile))
                          ( ;; w3m buffers
                           isw3m
                           (propertize trunc 'face 'helm-bookmark-w3m
                                       'help-echo isfile))
                          ( ;; gnus buffers
                           isgnus
                           (propertize trunc 'face 'helm-bookmark-gnus
                                       'help-echo isfile))
                          ( ;; Man Woman
                           (or iswoman isman)
                           (propertize trunc 'face 'helm-bookmark-man
                                       'help-echo isfile))
                          ( ;; Addressbook
                           isabook
                           (propertize trunc 'face 'helm-bookmark-addressbook))
                          (;; Directories (helm-find-files)
                           hff
                           (if (and (file-remote-p isfile)
									(not (file-remote-p isfile nil t)))
							   (propertize trunc 'face 'helm-bookmark-file-not-found
										   'help-echo isfile)
                             (propertize trunc 'face 'helm-bookmark-directory
                                         'help-echo isfile)))
                          ( ;; Directories (dired)
                           (and isfile
                                ;; This is needed because `non-essential'
                                ;; is not working on Emacs-24.2 and the behavior
                                ;; of tramp seems to have changed since previous
                                ;; versions (Need to reenter password even if a
                                ;; first connection have been established,
                                ;; probably when host is named differently
                                ;; i.e machine/localhost)
                                (and (not (file-remote-p isfile))
                                     (file-directory-p isfile)))
                           (propertize trunc 'face 'helm-bookmark-directory
                                       'help-echo isfile))
                          ( ;; Non existing files.
                           (and isfile
                                ;; Be safe and call `file-exists-p'
                                ;; only if file is not remote or
                                ;; remote but connected.
                                (or (el-patch-splice 1 1
									  (and (file-remote-p isfile)
										   (not (file-remote-p isfile nil t))))
                                    (not (file-exists-p isfile))))
                           (propertize trunc 'face 'helm-bookmark-file-not-found
                                       'help-echo isfile))
                          ( ;; regular files
                           t
                           (propertize trunc 'face 'helm-bookmark-file
                                       'help-echo isfile)))
          collect (if helm-bookmark-show-location
                      (cons (concat bmk sep (if (listp loc) (car loc) loc))
                            i)
                    (cons bmk i)))))

New release

Still loving el-patch for making my local customizations just a bit more robust.
Is there any chance you could put out a 2.5 (or 2.4.1) with the latest and greatest?
Thanks!

Add an extensible way to allow patching more forms

Instead of el-patch-defun, el-patch-defmacro, etc. being hardcoded, they should be defined using some macro that users can also use, e.g.

(el-patch-register defun ...)

where the ... will have to provide enough information for el-patch--classify-definition-type and el-patch--compute-load-history-items.

Autoloading a patch

Is there a way to define a patch, have it disabled by default and load the patch only when it is enabled for the first time?
In other words, I would like to be able to "autoload" the patch just like my functions are "autoloaded".

el-patch-validate-all fails with "apply: Cannot open load file: No such file or directory"

This is similar to #46.
When el-patch-validate-all is called, if I don't have the particular package installed in emacs, el-patch-validate-all fails with
apply: Cannot open load file: No such file or directory ...

I think it would be more helpful to ignore that particular patch and report it at the end. Below is an example:

(el-patch-feature outdated-package)
(with-eval-after-load 'outdated-package
  (el-patch-defun foo () ))

Handle advice correctly

I'm pretty sure that el-patch will not work correctly for functions that have advice, post-refactor.

You may want to change your example to a core emacs function

It's better to communicate with open source projects about your needs than to isolate yourself with your changes (which also means, if you need to advise a core emacs function, it may be worth bringing it up to emacs devel) - and packages can change much faster than emacs itself, which may make the patching unnecessary.

Unable to patch a function

I'd like to make a simple, two-line change to a rather involved function. The original is here and my attempt to patch it using el-patch and el-patch-swap is here.

The function comes from w3m-form.el of emacs-w3m [1]. It's what gets called when point is over something like a github clone web form on a github repo web page, where the user can use the c key to copy the contents of the web form to Emacs' kill ring. My patch just uses a custom function of mine (set-clipboard-contents) to also copy it to the X primary selection.

The change is to just line 40 in the original, using el-patch-swap in lines 40-43 in the patched version.

If I simply redefine this function using defun instead of el-patch or el-patch-swap but just changing

(define-key keymap "c" `(lambda nil (interactive) (kill-new ,value)))

to

(define-key keymap "c" `(lambda nil (interactive) (kill-new ,value)
                          (set-clipboard-contents ,value)))

it works just fine -- the contents of the form get copied to the X primary selection as expected.

But if I use el-patch and el-patch-swap to do it, nothing happens when I press the c key in a github clone form.

Also, after the regular re-define of the function, (describe-function 'w3m-form-expand-form) tells me just that w3m-form-expand-form is a Lisp function. and does not tell me where it was defined.

But, if instead of re-defining the function using defun I instead used el-patch, when I (describe-function 'w3m-form-expand-form) emacs tells me that w3m-form-expand-form is a Lisp function in ‘w3m-form.el’. so I'm not sure if emacs is even detecting that the function has been re-defined using el-patch (though maybe that's how el-patch is supposed to work).

I'm not sure how to troubleshoot this further, so I thought I'd ask here.

Thanks in advance for any help you can provide.

[1] - cvs -q -d:pserver:[email protected]:/storage/cvsroot co emacs-w3m

Don't apply .dir-locals.el when validating patches

If there's a .dir-locals.el file in the same folder as the file that defines a patched function, then it will be processed in the course of validation. This can cause annoying popup messages about possibly unsafe file-local variables.

Allow multiple patches for the same function

Presently, if you patch a single function multiple times, the definitions will work, but el-patch will be unable to validate any but the most recent patch. Ideally we would have a way to distinguish multiple patches to the same function.

One use-case for this when one version of a function is intended to be used before its file is loaded and a different version is to be used after.

Support renaming functions

We need to make sure things like

(el-patch-defun (el-patch-swap original-function derived-function)
    (&rest args)
    body...)

are supported correctly.

Add Travis

Even if we don't have unit tests yet, I would at least like to validate that there are no byte-compile or checkdoc warnings in the code.

Patch function without providing complete source in patch

I have a question regarding a possible extension of el-patch. In most cases, I just need to change a single form in a possibly long definition of a particular function. Such changes frequently break when the other code in the function, which I haven't patched, changes even though I know that such changes should not break my patch.

So I am wondering if it would be possible, in theory, to build a patch given a form. Something like this

(el-patch-patch
   'foo
   (function-call arg1 arg2 (el-patch-add arg3) )
)

and one would search the definition of foo for all forms which match (function-call arg1 arg2) and add an argument arg3. One could also add key forms to make sure that they exist in the function definition if such forms are necessary for the patch. Of course this assumes that one has the definition of foo rather than its compilation.

To be clear, I know that this is not implemented in el-patch, but I am wondering if people see a problem with such a methodology.

el-patch convenience directives to emulate advice-add

There are some functions that I can customize to my liking by either using around advice or overriding advice as opposed to copying the whole to my init file and using the el-patch directives. However, I'd still like the ability to track if any changes have occurred to the original definitions.

I know that if I wanted to emulate overriding, for example, I can do:
(el-patch-defun my-fun (args) (el-patch-swap (long original definition that I don't want to copy into my init file...) (new definition))

However I'd like to be able to do something like:
(el-patch-defun my-fun (args) (el-patch-override (new-definition)).

Where el-patch-override would be the same as el-patch-swap but using the whole definition.

I think what I'm asking boils down to:
Does el-patch provide a way to reference the definition original function definition without writing it out?
Does el-patch provide a way to define my own custom directives (so I could create el-patch-override)?

Doesn't handle lazy-loaded patches well

If I have a patch that is only loaded after clojure-mode, then:

  • It isn't validated by el-patch-validate-all unless I've loaded clojure-mode.
  • The patch is validated when I load clojure-mode, which is undesirable.

Suggested remedy:

  • Add el-patch-register-feature or somesuch.
  • Make el-patch-validation smarter.

Trying to patch long function without copying the old one

Hi, I am trying to find a way to add one line in the org-store-link function, but because of my unfamiliarity with elisp I am in the dark :)
I would be glad for any help or direction.
Here is my template attempt and defun attempt.
Patch is based on this answer

https://stackoverflow.com/questions/67604486/org-store-link-with-headline-as-description-using-custom-id-property
My attempt at template

(el-patch-define-template
  (defun (el-patch-swap org-store-link test-org-store-link))
  (el-patch-concat
...
    (el-patch-swap
      "(setq custom-id (org-entry-get nil \"CUSTOM_ID\"))"
      "((setq custom-id (org-entry-get nil \"CUSTOM_ID\"))
          (setq desc (org-link--normalize-string (org-get-heading t t t t)))
            (message \"patched funcion test message\")"

)
...
)
)

My attempt with function

(eval-after-load 'org
(el-patch-defun (el-patch-swap (old new) ()
(el-patch-concat
...
(el-patch-swap
         ((setq custom-id (org-entry-get nil "CUSTOM_ID"))
          (setq cpltxt
    (concat "file:"
      (abbreviate-file-name
       (buffer-file-name (buffer-base-buffer)))
      "::#" custom-id)
    link cpltxt))
         ((setq custom-id (org-entry-get nil "CUSTOM_ID"))
          (setq desc (org-link--normalize-string (org-get-heading t t t t)))
          (setq cpltxt
    (concat "file:"
      (abbreviate-file-name
       (buffer-file-name (buffer-base-buffer)))
      "::#" custom-id)
    link cpltxt))
) ;; end el-patch swap
...
) ;;end el patch concat
) ;;end el patch defun
) ;;end eval after load org

when-let and if-let are obsolete macros (as of Emacs 26.1)

When byte-compiling el-patch on emacs 26.0.90, I get these warnings:

In el-patch--compute-load-history-items:
el-patch.el:441:15:Warning: ‘when-let’ is an obsolete macro (as of 26.1); use
    ‘when-let*’ instead.

In el-patch-ediff-patch:
el-patch.el:709:17:Warning: ‘if-let’ is an obsolete macro (as of 26.1); use
    ‘if-let*’ instead.

In el-patch-ediff-conflict:
el-patch.el:728:17:Warning: ‘if-let’ is an obsolete macro (as of 26.1); use
    ‘if-let*’ instead.

In el-patch-unpatch:
el-patch.el:749:17:Warning: ‘if-let’ is an obsolete macro (as of 26.1); use
    ‘if-let*’ instead.

Clean up return values

This is the return value of an el-patch-defvar form that I evaluated:

((defun . radian--advice-inhibit-style-loading-message) (defun . radian--advice-inject-cljr-dependencies) (defun . radian--advice-cljr-message-eagerly) (defun . radian--advice-cljr-suppress-middleware-check) (defun . radian--reduce-cider-lag) (defun . radian--disable-aggressive-indent-on-save) (defun . radian--dump-nrepl-server-log) (defun . radian--advice-preserve-smex-sorting) (defun . ivy--dumb-flx-sort))

This is garbage. The return value should instead be the same as it would be if you had just evaluated a defvar form.

Unregistered definition type ‘defalias’

I am trying to advise the ielm function and I am getting the following fatal error.
Unregistered definition type ‘defalias’. Here is my code

(el-patch-defun ielm ()
      "Interactively evaluate Emacs Lisp expressions.
  Switches to the buffer `*ielm*', or creates it if it does not exist.
  See `inferior-emacs-lisp-mode' for details."
      (interactive)
      (let (old-point)
        (unless (comint-check-proc "*ielm*")
          (with-current-buffer (get-buffer-create "*ielm*")
            (unless (zerop (buffer-size)) (setq old-point (point)))
            (inferior-emacs-lisp-mode)))
        (el-patch-swap (pop-to-buffer-same-window "*ielm*")
                       (progn (split-window)
                              (pop-to-buffer "*ielm*")))
        (when old-point (push-mark old-point))))

Here is the stack trace

Debugger entered--Lisp error: (error "Unregistered definition type ‘defalias’")
  signal(error ("Unregistered definition type ‘defalias’"))
  error("Unregistered definition type `%S'" defalias)
  #f(compiled-function (definition &optional docstring-note) "Evaluate DEFINITION without updating `load-history'.\nDEFINITION should be an unquoted list beginning with `defun',\n`defmacro', `define-minor-mode', etc. DOCSTRING-NOTE, if given,\nis a sentence to put in brackets at the end of the docstring." #<bytecode 0xbe7bd1>)((defalias 'ielm (function (lambda nil "Interactively evaluate Emacs Lisp expressions.\nSwitches to the buffer `*ielm*', or creates it if it does not exist.\nSee `inferior-emacs-lisp-mode' for details." (interactive) (let (old-point) (if (comint-check-proc "*ielm*") nil (save-current-buffer (set-buffer (get-buffer-create "*ielm*")) (if (= 0 (buffer-size)) nil (setq old-point (point))) (inferior-emacs-lisp-mode))) (error "Can't use `el-patch-swap' outside of an `el-patch'") (if old-point (progn (push-mark old-point))))))) "This function was patched by `el-patch'.")
  (el-patch--stealthy-eval (defalias 'ielm (function (lambda nil "Interactively evaluate Emacs Lisp expressions.\nSwitches to the buffer `*ielm*', or creates it if it does not exist.\nSee `inferior-emacs-lisp-mode' for details." (interactive) (let (old-point) (if (comint-check-proc "*ielm*") nil (save-current-buffer (set-buffer (get-buffer-create "*ielm*")) (if (= 0 (buffer-size)) nil (setq old-point (point))) (inferior-emacs-lisp-mode))) (error "Can't use `el-patch-swap' outside of an `el-patch'") (if old-point (progn (push-mark old-point))))))) "This function was patched by `el-patch'.")
  (progn (puthash 'defalias '(defalias 'ielm (function (lambda nil "Interactively evaluate Emacs Lisp expressions.\nSwitches to the buffer `*ielm*', or creates it if it does not exist.\nSee `inferior-emacs-lisp-mode' for details." (interactive) (let (old-point) (if (comint-check-proc "*ielm*") nil (save-current-buffer (set-buffer (get-buffer-create "*ielm*")) (if (= 0 (buffer-size)) nil (setq old-point (point))) (inferior-emacs-lisp-mode))) (error "Can't use `el-patch-swap' outside of an `el-patch'") (if old-point (progn (push-mark old-point))))))) (or (gethash ''ielm el-patch--patches) (puthash ''ielm (make-hash-table :test (function equal)) el-patch--patches))) (el-patch--stealthy-eval (defalias 'ielm (function (lambda nil "Interactively evaluate Emacs Lisp expressions.\nSwitches to the buffer `*ielm*', or creates it if it does not exist.\nSee `inferior-emacs-lisp-mode' for details." (interactive) (let (old-point) (if (comint-check-proc "*ielm*") nil (save-current-buffer (set-buffer (get-buffer-create "*ielm*")) (if (= 0 (buffer-size)) nil (setq old-point (point))) (inferior-emacs-lisp-mode))) (error "Can't use `el-patch-swap' outside of an `el-patch'") (if old-point (progn (push-mark old-point))))))) "This function was patched by `el-patch'."))
  (el-patch--definition (defalias 'ielm (function (lambda nil "Interactively evaluate Emacs Lisp expressions.\nSwitches to the buffer `*ielm*', or creates it if it does not exist.\nSee `inferior-emacs-lisp-mode' for details." (interactive) (let (old-point) (if (comint-check-proc "*ielm*") nil (save-current-buffer (set-buffer (get-buffer-create "*ielm*")) (if (= 0 (buffer-size)) nil (setq old-point (point))) (inferior-emacs-lisp-mode))) (error "Can't use `el-patch-swap' outside of an `el-patch'") (if old-point (progn (push-mark old-point))))))))
  (closure (bootstrap-version t) nil (el-patch--definition (defalias 'ielm (function (lambda nil "Interactively evaluate Emacs Lisp expressions.\nSwitches to the buffer `*ielm*', or creates it if it does not exist.\nSee `inferior-emacs-lisp-mode' for details." (interactive) (let (old-point) (if (comint-check-proc "*ielm*") nil (save-current-buffer (set-buffer (get-buffer-create "*ielm*")) (if (= 0 (buffer-size)) nil (setq old-point (point))) (inferior-emacs-lisp-mode))) (error "Can't use `el-patch-swap' outside of an `el-patch'") (if old-point (progn (push-mark old-point)))))))))()
  eval-after-load-helper("/usr/intel/pkgs/emacs/26.1/share/emacs/26.1/lisp/ielm.elc")
  run-hook-with-args(eval-after-load-helper "/usr/intel/pkgs/emacs/26.1/share/emacs/26.1/lisp/ielm.elc")
  do-after-load-evaluation("/usr/intel/pkgs/emacs/26.1/share/emacs/26.1/lisp/ielm.elc")
  autoload-do-load((autoload "ielm" 1234208 t nil) ielm)
  command-execute(ielm)

Don't write build cache when init failed

Currently if there is an error during init, all the packages will be rebuilt next init because the build cache is written to disk (and not all of the packages had been enabled yet).

Question about el-patch-feature

Currently, el-patch-feature defines a function to load the feature and add it to el-patch-pre-validate-hook.
Isn't it better to keep track of features to load in a list variable (el-patch--features-to-require) and define a single function (el-patch--require-features) to load all of them? This has the added benefits of el-patch-require-* functions not showing up as candidates for elisp function completion.
What is your thought on this?

Suggestion: Use advice system for patching functions

When patching a function, I think it might be better (from an Emacs compatibility point of view) to define an advice with :override instead of over-defining the function.

I realize this would only work with functions, and not with other types (?), but since functions are the most patched type this might be worth it.

At the very least, something like this could be suitable

(defmacro el-patch-function (func-name &rest args)
  "Patches function FUNC-NAME.
Similar to `el-patch-defun' except the patch is applied as an
`:override' advice."
  (let* ((patch-name (make-symbol
                      (concat (symbol-name func-name) "@el-patch")))
         (new-args (cl-list* 'defun `(el-patch-swap ,func-name ,patch-name)
                             args)))
    `(progn
       (el-patch--definition ,new-args)
       (advice-add (quote ,func-name) :override (quote ,patch-name))
       ',patch-name)))

(and a similarly defined el-patch-define-function-template).

Using el-patch for new functions based on existing ones

Can el-patch be used to define new functions based on existing ones?

There's a certain function that I needed to use with slight modifications, but I didn't actually want to replace the original. So I made a new function based on it, with the modifications I needed. I'd still like to be notified when the original changes, however, so I can update my own modified copy.

I know this use case was not exactly what el-patch was designed for, but it's so close that I wonder if maybe there is some way it could be used here anyway?

el-patch not validating a patch that it should

When asking el-patch to show the conflict, I get this for actual:

(defun projectile-dir-files-external
    (root directory)
  "Get the files for ROOT under DIRECTORY using external tools."
  (let
      ((default-directory directory))
    (mapcar
     (lambda
       (file)
       (file-relative-name
        (expand-file-name file directory)
        root))
     (projectile-get-repo-files))))

and this for expected:

(defun projectile-dir-files-external
    (root directory)
  "Get the files for ROOT under DIRECTORY using external tools."
  (let
      ((default-directory directory))
    (mapcar
     #'(lambda
         (file)
         (file-relative-name
          (expand-file-name file directory)
          root))
     (projectile-get-repo-files))))

Note the #' near the lambda. I'm not sure why el-patch is expecting it.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.