ace-jump-mode icon indicating copy to clipboard operation
ace-jump-mode copied to clipboard

Display two characters simultaneously when excess options are available

Open ghost opened this issue 11 years ago • 15 comments

When there are too many options to cover with [a-zA-z], one types a single letter and waits for the screen to refresh before seeing a second letter to get to a destination. If the second letter were displayed along side the first letter without waiting for the screen to refresh (i.e. the style that vimium Google Chrome uses), there would be a significant reduction in the amount of time these multiple strokes take. I may repost some code if I decide to implement this myself over your source.

ghost avatar May 13 '13 22:05 ghost

I have ever thought about such kind of implementation, the potential problem is if the two candidate position are side by side, two more key suggestion cannot fit into one char space.

Such as, we have a word with six chars, such as"aabcde", and we use char mode to jump to the second 'a'. Now if we need more key than one key to jump to the second 'a', we need to render the screen as "XmXnbcde" of which Xm means to the first 'a' and "Xn" means to the second 'a'. But you should notice that, the length of the whole word for is changed when we render the selection mode for ace jump.

This will mess up the whole screen if many words need to change its length, which I think is not a good user experience. That is why I give up that idea of showing more candidate key simultaneously.

Of course, if you have any good suggestion to resolve such problem, that would be great, and I am also really expect that fancy idea :-)

winterTTr avatar May 18 '13 02:05 winterTTr

Right, I had the same concerns after reflecting on the idea. One easy fix would just be to revert the normal 1-character-at-a-time display if two searched characters are adjacent, as in your example. However, this would be somewhat inconsistent behavior, and people could easily get confused on whether they were seeing 1-character-at-a-time or 2-character-at-a-time displays, making the whole ace-jump-mode feel less robust. Nonetheless, there is still another option, which would be very easy to program in a iterative language and of course possible in lisp. Remember that we have 26*25 = 650 options for consecutive keystrokes using lowercase letters. That is a LOT of combinations, and, as you have mentioned previously, should more than suffice for one screen of matches. But how can we use this to our advantage? My recommendation is as follows. Say that our document looks like this:

aardvarks are awesome hahahaaaa

Now what we are worried about is the char-select mode for 'a'. Because we want people to use two lowercase keys EVERY TIME - using only 1 key for fewer than 26 matches would again split behavior, and we want a single, quick, and efficient mapping of visual display to necessary key strokes each and every time. So we would like every match to have a lower case letter placed on top of it and on the following slot. Now the difficulty is generating unique keys from our large space of 650. I have a very nice strategy for doing this, but first let me give you an example of how it would work on the above document:

aardvarks are awesome hahahaaaa
012dv02rks 03e 04esome h050607(14)(21)(28 mod 23)   
012dv02rks 03e 04esome h050607(14)(21)5
qwedvqeks qre qtesome hqyquqigcy

Note that any 'a' may be reached by a unique combination of 2 keystrokes. The mapping from number labels to letters could be done with standard alphabetic ordering (i.e. 0->a, 1->b, 2->c), but I used qwerty left-then-down numbering to allow people to type to very close letters if there are few matches. This could be done either way. So its easy to see a direct mapping from numbers to letters, but how do we generate the numbers?

If you look closely, you will see my suggested pattern. First, go through the text and collect all consecutive matches. For each cluster of consecutive 'a's, give it a number according to its position wrt the other clusters. If there are fewer than 23 (which is good for math reasons because it is the greatest prime < 26) clusters, then our job is easy. Start with the first cluster, and set each a equal to 0 + (index inside cluster) * (index of cluster among other clusters + 1) mod 23. Thus, the first cluster has number offset by 1, the second by 2, and so on and so forth. Thus, by typing two consecutive keys, the typist specifies which (cluster number) by the difference of his two keys, (key 2) - (key 1) and the place within the cluster by the (number of the first key)/(cluster number). Now, because 23 is prime, we can have clusters of length up to 23 before we get any problems, no matter what number the cluster is (i.e. 0,1,2,... is 23 long before repeating, but so is 0,7,14,... mod 23, which you can check). Also, we are using almost the entire lowercase keyboard, and the algorithm is essentially making optimal use of combinations.

Now, there is only one remaining problem to deal with - what if there are more than 23 clusters. This is a very forsee-able problem, especially if we have strings such as:

hahaaahahahahahahahahahahahahahahahahahahahahahahahahahahahaha. 

How do we deal with this? Well, imagine that we have a cluster with number 24. Then perhaps we could associate the cluster with number 1, which perhaps looked something like aaah = (0,2,4,6) = (q e t u). Why not just pick up where number 1 left off, and continue with (6 8) = (u o). The implementation of this is pretty straightforward - just collect clusters mod 23 again and assign each match's (index inside cluster) according to its (index inside family of clusters with equal cluster number mod 23). Now this will break if a family of cluster sharing a cluster number mod 23 has more than 23 matching 'a's in total. So how do you deal with this case? Well, you could imagine a hacked solution to move around clusters into other families, but that could get messy. Instead, I would recommend that if a family of clusters has more than 23 totals matches, that each cluster in the family only displays ace jump options for its first member.

Let me know if you have any recommendations or insights into this strategy. I am not too familiar with loops in lisp, but this might be a good way to learn. Please let me know if you intend to code this, as the integration would be more robust if you as the author of ace-jump-mode wrote it. For reference to an implementation I wrote in python2.7, see my git at https://github.com/Russell91/src/blob/master/python/ace-jump-2-chars.py

ghost avatar May 20 '13 00:05 ghost

You idea is awesome, I NEVER try to resolve this problem like this. That's amazing.

You know, the only thing which I always persist in, is I want to people use ace jump without any training, that means I want to make it direct and intuitive.

So with your solution, we may potentially need to "teach" the user about how to use it, and you know, many user never read any note, manual or even FAQ before they use something. And on the other hand, if there is more than 23 clusters, ace jump will need to enter original mode and then "two char simultaneously" mode, this may also make user confusing about mode changing. So we may need to think how to make those things intuitive to final user.

But anyway, mode changing and teaching user is worthy if finally user really like this way, so I definitely would like to try to implement your idea firstly.

Recent is kind busy for my final examination of this term, I may try to implement some prototype after middle of June. So before that, it is great appreciated to share any idea or concern about the way your provide here.

Thanks so much for your fancy idea, I like it :+1:

winterTTr avatar May 21 '13 02:05 winterTTr

Hey guys, I think this is a really important problem to solve. Stacked, two-character hints really knocks me out of my Emacs flow. The real problem isn't that typing two chars is harder than typing one -- it's that you have to type one character, read one, and then type another. That's two round trips to the brain, which is much slower than touch typing two keys in one go. Even worse, when you are typing the first hint character, you don't know if there's going to be a second. You have to wait and see.

Given that this bug has been open for awhile, I think a good way to break the inertia is to find an initial approach that's simple enough to actually be implemented. Here's a proposal:

  1. Add an option to disable "hint key stacking" (today's behavior) so it's opt-in. We can make it the default after we've tested it in practice.
  2. In every case where the two-char hint A is directly adjacent to hint B (i.e. the two hints collide), don't show hint B.
  3. Color the two-char hints yellow instead of red, to make it clear that you need to type two characters.

This simple approach has a few benefits:

  • The UX is self-evident, so there's no training or documentation that needs to happen. I love this design goal of Ace jump.
  • Since it avoids handling the sticky collisions problem, I'm hoping it's easy to agree on and implement.
  • While step 2 has the drawback of not showing some hints, I think that case is rarely a real concern, especially when using ace-jump-word-mode. If I wanted to get to the third "ha" in hahaha by using ace-jump-char-mode, I would be happy having to jump to the first "ha" and then move to the right, if it meant I could avoid stacked hints.

I think a simple approach like this gets us most of the benefit and drastically improves the UX.

@winterTTr, in the spirit of coming up with a UX that's simple enough to actually have a chance of getting implemented, do you have further ideas to simplify it?

philc avatar Nov 23 '13 06:11 philc

Bump @winterTTr

philc avatar Dec 17 '13 23:12 philc

another way to resolve the same problem is to use a technique that vim-sneek uses. it's very simple and very effective.

  1. type 2 characters, not 1.
  2. if there's only 1 match, jump to it. if there's more than 1, display all available options like the previous algorithm.

bling avatar Feb 21 '14 13:02 bling

here's a quick attempt...works extremely well already. if this looks good i can create a variable to make this configurable (default off) and submit a PR. thanks.

diff --git a/ace-jump-mode.el b/ace-jump-mode.el
index ef4391f..0b2310f 100644
--- a/ace-jump-mode.el
+++ b/ace-jump-mode.el
@@ -819,22 +819,23 @@ word-mode and char-mode"

 ;;;###autoload
-(defun ace-jump-char-mode (query-char)
+(defun ace-jump-char-mode (query-char1 query-char2)
   "AceJump char mode"
-  (interactive (list (read-char "Query Char:")))
+  (interactive (list (read-char "Query Char (1/2):") (read-char "Query Char (2/2):")))

   ;; We should prevent recursion call this function.  This can happen
   ;; when you trigger the key for ace jump again when already in ace
   ;; jump mode.  So we stop the previous one first.
   (if ace-jump-current-mode (ace-jump-done))

-  (if (eq (ace-jump-char-category query-char) 'other)
+  (if (or (eq (ace-jump-char-category query-char1) 'other)
+          (eq (ace-jump-char-category query-char2) 'other))
     (error "[AceJump] Non-printable character"))

   ;; others : digit , alpha, punc
-  (setq ace-jump-query-char query-char)
+  (setq ace-jump-query-char query-char1)
   (setq ace-jump-current-mode 'ace-jump-char-mode)
-  (ace-jump-do (regexp-quote (make-string 1 query-char))))
+  (ace-jump-do (regexp-quote (string query-char1 query-char2))))

edit: oops, looks like this was already suggested in #23 -- almost the exact same code change as well.

bling avatar Feb 23 '14 16:02 bling

Btw, here's how this looks in easy-motion:

image

Seems to me this is the direction we should be heading with ace-jump.

bbatsov avatar Jul 24 '14 15:07 bbatsov

Agreed @bbatsov; the easy motion implementation looks well done.

philc avatar Jul 24 '14 18:07 philc

@bbatsov @philc It looks pretty but it's annoying. 2-char input solves the multi-step annoyance.

justinmk avatar Jul 24 '14 19:07 justinmk

+1 for easy motion implementation. I would really love to see this feature in master.

antonyshchenko avatar Dec 11 '14 12:12 antonyshchenko

@justinmk you might look into evil-snipe for multi-char searches (ask if it can be implemented for vanilla emacs there).

I, personally would like to see behavior similar to EasyMotion, because of evil-easymotion.

PythonNut avatar Jan 12 '15 05:01 PythonNut

Is there any update about getting this feature?

ReneFroger avatar Jan 18 '15 15:01 ReneFroger

Been using the multi-char searches solutions, and it works well except for when you want to jump to certain places (like a single character '{'), then for me, It's no longer automatic. For that reason I've given up on it. The coloring idea is a great one, since it's clear whether you need two characters, or whether there are two matches side by side.

mikedmcfarland avatar Mar 31 '15 14:03 mikedmcfarland

Btw, the newer avy-jump supports this https://github.com/abo-abo/avy

bbatsov avatar May 08 '15 14:05 bbatsov