reddit / emacs

Some code to turn image data on the clipboard into an emacs buffer, and a question.

deltille · 2022-04-26 04:54
I've spent the past couple of days noodling with this idea-- I knew emacs could access the X11 clipboard, I knew emacs could display images, and I knew emacs could deal with the binary data behind said images fairly well, so it stood to reason it would be possible to get directly from 'screenshot' (or copy-to-clipboard image out of a web browser, etc.) to an emacs buffer with no temp-file go-between like every single emacs screenshot tool I've seen. (this will only work on X11, consequently) (defun d/clipboard-to-image-buffer () (interactive) (set-clipboard-coding-system 'raw-text) (when (get-buffer "*emacs-ss*") (kill-buffer "*emacs-ss*")) (let* ( (x-select-request-type 'image/png) (sel (gui--selection-value-internal 'CLIPBOARD)) ) (if sel (with-current-buffer-window (get-buffer-create "*emacs-ss*") (toggle-enable-multibyte-characters) (set-buffer-file-coding-system 'raw-text) (insert sel) (image-mode)) (error "No image/png data on clipboard.")))) I've learned a fair bit about how X handles its clipboards in the process. I can't say it's more than I wanted to know, but I can definitely say it's more than I would have wanted to know if I didn't have other projects leaning on it. (Also a fair bit about emacs' specificities about its interactions with the system clipboard. I only tracked down `set-clipboard-coding-system` today, from this: http://dinasis.com/oliver/Learning_GNU_Emacs/gnu3-CHP-2-SECT-4.html I could tell from watching debug traces that some encoding was happening at the 'clipboard access' step and tripping me up but couldn't make much more progress until stumbling on that...) My question is primarily: **Is there code that does this hiding in plain sight?** I have been searching on-and-off for something like a year now and have not seen a single 'capture screenshot to emacs for one thing or another' script that didn't rely on there being a capture to file somewhere in the middle. I would get a lot of use out of this, enough to justify writing out a lot more code for it but I'd like to make sure I'm not wasting my time first. Secondarily, if someone with better elisp chops than me is reading this, I'd love to hear your input on coding style, or especially where I'm overlooking an existing function. I'm getting to the point I'd like my code to be somewhat readable *and* do its job instead of just the latter. eg. it *feels* like set-clipboard-coding-system should be in a let, it's something I'd normally put in one, but since it's not a *variable* it's not amenable to that and I'd love to know if there's a convention for handling this scenario. eg. 2: any better way to handle cleaning up (or for that matter, creating/naming) the emacs-ss buffer in between goes. Stuff like that.
franburstall · 2022-04-26 06:43
`set-clipboard-coding-system` simply sets the variable `selection-coding-system` so you could let-bind that?
deltille · 2022-04-26 06:53
how did I not see that! I'll account for that when I refactor :)
7890yuiop · 2022-04-27 06:07
I'd suggest you get rid of `(when (get-buffer "*emacs-ss*") (kill-buffer "*emacs-ss*"))` in favour of calling `(erase-buffer)` a little later (although I don't know for sure that there isn't some advantage to your approach for this use-case). Definitely never use a toggle function in code when you can ask for the *specific* state you want, which IIUC in this case is `(set-buffer-multibyte nil)`? In general, toggling leaves you exposed to weird future bugs, and should only be used in commands *for* toggling something. If you find yourself using a toggle function, you can typically just look at its code to see what *it* is doing for your scenario, and do the same thing. Your formatting is odd in places. `(interactive)` should be on its own line, and it's not normal to put the `(` and `)` on separate lines for your `let*` declarations. The indentation is also a bit broken, although maybe that's just a reddit accident.
deltille · 2022-04-27 08:07
Many thanks, I appreciate the input. It's not a reddit accident, I've honestly been really unclear on the maxims for formatting/linebreaking and indenting lisp in general. If there's a specific style guide you'd recommend I'm all ears. Glad to see my intuition 'it feels weird to not just use a let here' was on the dot, another commenter pointed out the variable I want for the other toggle I'm setting. I've been putting the opening/closing parens on their own lines in a lot of statements like that to make adding/removing elements less of a nuisance. Is there a good answer to this? The only answers I can think of are 'clean up at the end' or 'use some kind of navigation that understands sexps' the latter of which I've struggled to find something I like for. (erase-buffer) would work fine yes, that was one of the clunkiest parts I was hoping for input on :) Is there any specific reason for interactive on its own line? I notice it now that I'm looking but I'm curious if there's reasoning besides 'everything else does it that way'. Easier visual distinguishment between commands vs functions?
Phil-Hudson · 2022-04-27 08:33
Since you ask, here's a fine point of style which may simply be a matter of personal taste, but I would write: ... (unless sel (user-error "No image...")) (set-buffer-file-coding-system ...) `unless` enables "straight-ahead" programming here (getting rid of as many branches and loops as possible). `user-error` is more appropriate than `error` *in this case*; check the doco.
Phil-Hudson · 2022-04-27 08:52
On my reading, `with-current-buffer-window` erases the buffer anyway on entry, so no need for your code to do so.
Phil-Hudson · 2022-04-27 09:00
On my reading, again, it seems as if you are missing the required second and third arguments to `with-current-buffer-window`, which should probably both be `nil`. If that is correct, then I wonder whether/how your code could ever have run correctly.
Phil-Hudson · 2022-04-27 09:05
You should get in the habit of always writing a documentation string for every function, command, variable and constant. I'd recommend enabling the built-in `checkdoc-minor-mode` for all Elisp code: (require 'checkdoc) (add-hook 'emacs-lisp-mode-hook #'checkdoc-minor-mode)
Phil-Hudson · 2022-04-27 09:29
Here's my version, inspired by your excellent work. Really, don't be discouraged by all the suggestions, you're already contributing something useful and I for one appreciate it. (defun ph/x11-clipboard-to-image-buffer () "Display the image on the X11 clipboard in an Emacs buffer." (interactive) (let* ((selection-coding-system 'raw-text) (x-select-request-type 'image/png) (sel (gui--selection-value-internal 'CLIPBOARD))) (unless sel (user-error "No %s data on clipboard" x-select-request-type)) (with-current-buffer-window (get-buffer-create "*emacs-ss*") nil nil ;; Not needed, works same with either 1 or 0 ;;(toggle-enable-multibyte-characters 1) (insert sel) (image-mode))))
deltille · 2022-04-27 10:28
Huh. I had to look at with-current-buffer-window's doc. I now have no idea how the hell it's working either, as I thought the arguments were more like (with-current-buffer-window BUFFER-OR-NAME rest BODY-FORMS). That is... *something.* It definitely does run though...
deltille · 2022-04-27 10:30
Good to know, I'm still no good at reading macros so this is useful info to me. On squinting at it I see the call to temp-buffer-window-setup which is calling erase-buffer though, so that tracks.
deltille · 2022-04-27 10:30
Good point, that reads a lot better than the 'oh, and by the way, if nothing is there...' after-thought sort of phrasing it has now.
deltille · 2022-04-27 10:33
I'm not, I invited it for a reason. :) I do appreciate you spending the time to pore over my code, it was exactly what I was looking for. To spare you one less spam reply, I didn't know about checkdoc, this will be a big help. I already have eldoc set up to show me docstrings proactively but the usecase is a bit different.
7890yuiop · 2022-04-27 10:49
The formatting questions can be answered by marking/selecting your whole function and typing `TAB` to indent the region. You'll find out why `(interactive)` should be on its own line, and after fixing that you'll see what the indentation should look like. Moreover, you should rely on Emacs to indent code for you, so that you don't have to think about it. (Line breaks are your call, though.) See `C-h i g (emacs)Expressions` regarding navigation by s-expressions. I use most of those commands a *lot*. Some people also swear by the likes of paredit to maintain a balanced structure throughout the editing process (I've never used it myself, although I'm sure it's good once you're used to it).
deltille · 2022-04-27 11:37
It goes to show how well-hidden something can be in emacs but hiding in plain sight, I had *no idea* that was just sitting on tab this entire time. I don't see the self-evident explanation of why (interactive) goes on its own line though. Is it to improve readability when the argument list is long?
7890yuiop · 2022-04-27 11:48
Aside from the fact that any elisp programmer will say it looks utterly bizarre :), the indentation is messed up when it's not on its own line. It's the difference between: (defun d/clipboard-to-image-buffer () (interactive) (set-clipboard-coding-system 'raw-text) ...) and the expected: (defun d/clipboard-to-image-buffer () (interactive) (set-clipboard-coding-system 'raw-text) ...) Moreover, if you document your function (as indeed you should), the docstring comes before the `(interactive)` declaration, at which point the idea of *not* putting a line break beforehand becomes even weirder.
deltille · 2022-04-27 21:39
Ohhhhhhh, okay, I see. I must've not messed with it enough. Now I get it- having a third list in the first line confuses the indenter quite a lot. Thanks for elaborating.