I have a image displayed as inline image in org-mode. I would expect after the image file modified by others, emacs detect such modify and refresh the buffer to show the updated image.
How can I make that happen?
[Updated answer after @dalanicolai's comments - this implements the lexically scoped solution which is most suitable if separate buffers are using their own separate images directory. See @dalanicolai's comment for another idea that is probably more suitable if there is a single images directory that is shared by multiple buffers. I did not implement that idea because I believe that the "separate images directories" setup is (or should be) the more common case - but that's IMO and YMMV.]
With thanks to @dalanicolai for pointing out the file notifications capabilities of Emacs, here is a fairly simple implementation:
;;; -*- lexical-binding: t -*- (defun my/org-redisplay-create-file-watcher (imagedir) (interactive "f") (let ((my/org-redisplay-inline-images-buffer (current-buffer))) (cl-flet ((my/org-redisplay-inline-images-cb (_event) (with-current-buffer my/org-redisplay-inline-images-buffer (org-redisplay-inline-images)))) (setq-local my/org-redisplay-inline-images-file-watch (file-notify-add-watch imagedir '(change) #'my/org-redisplay-inline-images-cb))))) (defun my/org-redisplay-delete-file-watcher () (interactive) (if (boundp 'my/org-redisplay-inline-images-file-watch) (setq-local my/org-redisplay-inline-images-file-watch (file-notify-rm-watch my/org-redisplay-inline-images-file-watch)))) (add-hook 'kill-buffer-hook #'my/org-redisplay-delete-file-watcher) While in your Org mode buffer, you can call my/org-redisplay-create-file-watcher with the path of the directory where the images are stored. It will create a file watcher watching that directory and it will store the watcher ID in a buffer-local variable, so that the watcher can be cleaned up when the buffer is killed. The watcher uses a callback function which calls org-redisplay-inline-images: whenever the watcher detects any change in the specified directory, it will invoke the callback which will redisplay the inline images. Note that any change will trigger a call: the callback does not know what files you are using and does not check that a relevant file has changed; it will fire a redisplay whatever the change (e.g. if somebody creates an unrelated file in the directory), but it will certainly catch changes to the relevant files and trigger a redisplay. You can limit the possibility of unnecessary redisplays by storing only the image files in the specified directory.
Note also that the buffer that you call the -create-... function from (the Org mode buffer with the images) is captured in the callback closure (note the lexical-binding setting at the top of the file) so that the redisplay can be done in the correct buffer (I have no idea what buffer is the current buffer when the callback is called). And we also add the cleanup function to the kill-buffer hook so that the watcher can be removed when the buffer is killed.
I saved the above code in a file redisplay.el, byte-compiled it and I tested it by starting with
emacs -Q -l redisplay.elc -l ob-python test.org where test.org is shown below. It contains a local variables section at the end which automatically calls my/org-redisplay-create-file-watcher with an argument of ./images, a subdirectory of the current directory where the images are stored. After you confirm that it's OK to evaluate the local variables section, the buffer is opened. It will try to inline the images but since your ./images directory is currently empty, it will not succeed. The test.org file provides three python code blocks: the first two produce different images but they store the image they produce in the same file ./images/foo.org; the third code block uses a different file ./images.bar.org. So you can run the third code block with C-c C-c and then run alternately the first and second code blocks to change the image in the foo.org file and see that it is automatically updated.
Here's the test.org file:
#+STARTUP: inlineimages * Some images [[file:images/foo.png]] and [[file:images/bar.png]] * Code Two code blocks, each producing a PNG image named "images/foo.png" - depending on which code block we run, we can change the image. #+begin_src python :results silent file link :var file="./images/foo.png" import matplotlib.pyplot as plt fig, ax = plt.subplots() fruits = ['apple', 'blueberry', 'cherry', 'orange'] counts = [40, 100, 30, 55] bar_labels = ['red', 'blue', '_red', 'orange'] bar_colors = ['tab:red', 'tab:blue', 'tab:red', 'tab:orange'] ax.bar(fruits, counts, label=bar_labels, color=bar_colors) ax.set_ylabel('fruit supply') ax.set_title('Fruit supply by kind and color') ax.legend(title='Fruit color') plt.savefig(file) return file #+end_src #+begin_src python :results silent file link :var file="./images/foo.png" import matplotlib.pyplot as plt import numpy as np def koch_snowflake(order, scale=10): """ Return two lists x, y of point coordinates of the Koch snowflake. Parameters ---------- order : int The recursion depth. scale : float The extent of the snowflake (edge length of the base triangle). """ def _koch_snowflake_complex(order): if order == 0: # initial triangle angles = np.array([0, 120, 240]) + 90 return scale / np.sqrt(3) * np.exp(np.deg2rad(angles) * 1j) else: ZR = 0.5 - 0.5j * np.sqrt(3) / 3 p1 = _koch_snowflake_complex(order - 1) # start points p2 = np.roll(p1, shift=-1) # end points dp = p2 - p1 # connection vectors new_points = np.empty(len(p1) * 4, dtype=np.complex128) new_points[::4] = p1 new_points[1::4] = p1 + dp / 3 new_points[2::4] = p1 + dp * ZR new_points[3::4] = p1 + dp / 3 * 2 return new_points points = _koch_snowflake_complex(order) x, y = points.real, points.imag return x, y x, y = koch_snowflake(order=5) plt.figure(figsize=(8, 8)) plt.axis('equal') plt.fill(x, y) plt.savefig(file) return file #+end_src Here's another image - we produce it once and leave it alone thereafter: #+begin_src python :results silent file link :var file="./images/bar.png" import matplotlib.pyplot as plt import numpy as np # Fixing random state for reproducibility np.random.seed(19680801) dt = 0.01 t = np.arange(0, 30, dt) nse1 = np.random.randn(len(t)) # white noise 1 nse2 = np.random.randn(len(t)) # white noise 2 # Two signals with a coherent part at 10 Hz and a random part s1 = np.sin(2 * np.pi * 10 * t) + nse1 s2 = np.sin(2 * np.pi * 10 * t) + nse2 fig, axs = plt.subplots(2, 1, layout='constrained') axs[0].plot(t, s1, t, s2) axs[0].set_xlim(0, 2) axs[0].set_xlabel('Time (s)') axs[0].set_ylabel('s1 and s2') axs[0].grid(True) cxy, f = axs[1].cohere(s1, s2, 256, 1. / dt) axs[1].set_ylabel('Coherence') plt.savefig(file) return file #+end_src * Local variables # Local Variables: # eval: (my/org-redisplay-create-file-watcher "./images") # End: Incidentally, I use -l ob-python in the command line above to enable the Org Babel Python implementation, since the three code blocks that produce the images are written in python. The code is adapted from some examples found on the Matplotlib Gallery page.
I've done some more testing with the current (lexically-scoped) implementation, using two buffer with separate image subdirectories and modifying each subdirectory externally. It seems to work fine, but if you find problems, please let me know.
inotifycan be used for such things - there are similar mechanisms on other OSes. Then you need a subprocess in Emacs that will get the notification and refresh the buffer.file-notify-add-watchwhich you can pass a callback function. You can read more about it here.file-notify-add-watchdoes indeed make it easy. @dalanicolai: please make your comment into an answer.file-notify-add-watchwould only make up a small part of a complete answer for how to achieve it. A full answer would also provide code for the callback function, and possibly also some code to make org-mode create/manage such watchers automatically. Would you agree?eval-ed by hand or through file local variables). It would have to save the Org mode file's buffer and thefile-notifydescriptor (probably as buffer-locals), the first so that the callback can runorg-redisplay-inline-imagesin the correct buffer and the latter so that the watch can be removed. For now, removing the watch manually might be enough, but it could probably be done automatically when the buffer is killed.