21

I'd like to display 3 lists of words on separate lines horizontally along the bottom (although top would work too) of every emacs frame I have open. I've thought of 6 ways to do this, and they all have issues:

  1. My first thought was to add a line to my mode line, but AFAICT you can't use the newline character in a mode line, it just gets converted to "^J".

  2. My second thought was to have the line across the top of the screen and use the header line, but it doesn't support the newline character either.

  3. I could display an overlay over the last 3 lines of the window, but making this robust seems hard -- scrolling would need to be triggered when point reached the overlay rather than the real end of the window, and I'd have to constantly reposition the overlay since overlays are in text space not window space.

  4. I could try to make dedicated windows at the bottom of the frame. I have tried coding this up but it's not very robust either, it doesn't seem to work right when a frame already contains split windows and I've had to rebind C-x,1 to a custom version of delete-other-windows that ignores my special windows and I'm sure there are other corner cases. Also when a help window pops open now it pops open vertically because it thinks there's already a horizontal split (which technically there is but it's only to display a one line window).

  5. I could have a dedicated frame for this, but then my config won't work in terminal mode, and I'd have to script my window manager to handle keeping it along the bottom of the screen, making it unselectable, not affecting layout, etc. etc.

  6. I could insert the text for the 3 lines directly into the minibuffer. I got this partially working, I can grow the minibuffer to accomodate the 3 lines, and I can display them. However, any time any message is echo'd the lines disappear until I issue another command at which point they re-appear. Ideally the 3 lines and the echo area would not overlap so I could see both. This would be less annoying if I could reliably filter which messages go to the echo area -- I found a solution on EmacsWiki but it doesn't appear to work for messages that originate in the emacs C source (specifically I'd like to get rid of the file saving messages because I autosave often on a timer).

For context, my goal is to constantly have a display of the most frequently used words in the current buffer, the words nearest point in the current buffer, and the words most recently used in the current buffer. I intend to be able to insert them into the buffer via voice commands. So I could say "nearest 2" and have it pick the second item from the list of words nearest point and insert it. I only care about the word lists being visible for whatever buffer I'm currently editing. I don't want to use the pop-up windows used by the various code completion modes because I need the lists always visible.

Joseph Garvin
  • 2,061
  • 8
  • 21
  • Good question, and well posed. Hope you get some useful suggestions. – Drew Dec 19 '14 at 23:53
  • I wonder about the other parts of #5, besides the terminal-mode need (which is major). You could use a dedicated frame that is positioned at the bottom (no problem). What do you mean by *unselectable*, and why do you need that? If you mean read-only, then that is no problem either. What do you mean by *not affecting layout*? In sum, #5 is not too clear to me. – Drew Dec 19 '14 at 23:57
  • @Drew: I mean that I drive my WM using the keyboard, and it's unlikely I would ever want to give that frame focus on purpose, so I'd want my next-window/previous-window bindings to skip over it. Likewise I'd want the window layouts to act as if that frame was just part of a panel/taskbar. Edit: everywhere in this comment I said 'window' I mean X window, not emacs window ;p – Joseph Garvin Dec 20 '14 at 02:45
  • One other possibility suggested to me is just using the minibuffer. I have no idea if it's possible to passively display text down there without it interfering with everything else trying to use it though... – Joseph Garvin Dec 20 '14 at 03:06
  • Added a note about trying to use the minibuffer. – Joseph Garvin Dec 20 '14 at 05:20
  • Is something like `speedbar` acceptable? – abo-abo Dec 20 '14 at 08:51
  • @JosephGarvin advise message to always append the words you want to the end of its arguments. – Malabarba Dec 20 '14 at 08:54
  • I think that because neither `speedbar` nor `ediff` weren't successful at doing this (they both opt for separate frame), I'd say that's probably not possible. But the way you describe it, I think you could approach it in the same way `ispell` does (when it displays the options buffer). I.e. whenever you want to interact with the groups of words, you'd need to invoke the display of that buffer, and after you finished the interaction, that buffer would disappear, so you wouldn't focus it by accident. – wvxvw Dec 20 '14 at 09:40
  • Wrt #5: You can no doubt make `next/previous-window` etc. do what you want. As long as you use Emacs (and not some direct WM access) to do things you should be OK. Dunno what "act as if..." means. AFAICT, so far #5 sounds doable, to me. – Drew Dec 20 '14 at 17:01
  • FWIW, #3 sounds the most reasonable to me, so far. You might even be able to get something working quickly as a POC by fiddling with `overlay-arrow-string` and `overlay-arrow-position` (dunno). For that, see the Elisp manual, node [`Overlay Arrow`](https://www.gnu.org/software/emacs/manual/html_node/elisp/Overlay-Arrow.html). – Drew Dec 20 '14 at 17:07
  • Here is a link to a sample test that I use to calculate `window-start` and `window-end` prior to redisplay: http://stackoverflow.com/a/24216247/2112489 It's not every situation, but covers the most common situations. The notion is to limit the calculations to only one set -- without some type of similar system, both hooks may be called and efforts are duplicated -- I think the `window-scroll-functions` hook may even be called more than once on some occasions -- the test mentioned in the link endeavors to ensure it is only one set of calculations. – lawlist Dec 20 '14 at 18:09
  • I had to tweak `ispell` a bit for the minibuffer because I wasn't happy with the code used to shrink and expand the minibuffer, but you may want to take a look at what happens when you press the `?` when `ispell` is running -- it increases the size and decreases the size of the window. It also erases what's in the minibuffer and then displays it's own text. It would probably be fairly simple to detect a new entry to the minibuffer -- e.g., when a `message` appears, and then you can put your lines above that and then reduce the minibuffer size again when that `message` is no longer needed. – lawlist Dec 20 '14 at 18:41

2 Answers2

8

With a lot of hacky experimentation I was able to get #6 (using minibuffer text) to a 'good enough' working state. Here's a screenshot:

Screenshot of belt in action

There are several key parts to make this work:

  • Inserting text into the minibuffer surprisingly almost does the right thing out of the box. Text inserted there will actually show up.
  • By making the text be the 'after-string of an overlay instead of regular text you make it unselectable and don't have to worry about the cursor accidentally getting in it.
  • To make minibuffer prompting commands work correctly, you have to inhibit inserting the text/overlay when the minibuffer window is active.
  • If you try to resize the minibuffer with normal window resize functions you will get errors about windows being too small, if you use the undocumented md-resize-minibuf function then you can resize to the exact number of lines you want, as long as you have set resize-mini-windows to nil first.
  • To solve the lists disappearing whenever there is a message you have to advise the message function to intercept messages. Then you insert them into the minibuffer yourself. You also have to look at the current-message variable, which stores whatever last showed up in the echo area (surprisingly the echo area and the minibuffer are technically distinct and some C source code functions print directly to the echo area without going through the message function). The code I provide below for this is imperfect, messages persist longer than they normally would which I still need to investigate (checking the last entry in *Messages* may be simpler and more robust) but this is 'good enough' for now.

Here's a link to my implementation with an example belt displaying the kill ring. Eventually this will be part of a proper project: https://gist.github.com/jgarvin/ce37d08654978fd7e4c9

This is my first time writing any significant quantity of elisp so quality is probably subpar, but it works.

Joseph Garvin
  • 2,061
  • 8
  • 21
1

Neither the mode-line nor the header-line can be multiple lines, unfortunately. I have asked about this before and there is (at least wasn't) any hidden option to make this work. So 1 and 2 are out. I also feel like 3 and 6 are hacks that won't make you happy in the long run. 3 and 4 seem like fine approaches, but to get them to work reliably will be quite an investment.

So I would recommend that you first bring this up on emacs-devel. In my experience things do get implemented eventually if you bother to carefully explain what you want and why that is a good thing. It might take some time, at least until the next release, but if you are fine waiting a bit or using the development version, then you could possibly get exactly what you want, with much less effort.

tarsius
  • 25,298
  • 4
  • 69
  • 109
  • Thanks for the suggestion to contact emacs-devel. Even though I came up with a solution it was pretty hacky and it would be nice to have a real API for directly drawing on screen coordinates, so when I have the time I'll probably shoot an email their way. – Joseph Garvin Jan 13 '15 at 19:58