Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow for case insensitivity #64

Closed
rodneykeeling opened this issue Oct 20, 2021 · 10 comments
Closed

Allow for case insensitivity #64

rodneykeeling opened this issue Oct 20, 2021 · 10 comments

Comments

@rodneykeeling
Copy link

I haven't seen a config option for this nor old issue request this, so I'm wondering if this was a consideration before.

Currently, beacons are lit only if they match the case of the characters provided. So, for example, if I want to target None, I have to type sNo. I think it'd be great if there was a case insensitive option such that typing sno would target the text None.

@ggandor
Copy link
Owner

ggandor commented Oct 20, 2021

https://github.com/ggandor/lightspeed.nvim/tree/176ab1e0e5e9a044a36b38d2dc5db357c2add9b2#ignore-case-for-2-character-search

I wanted to implement smartcase-like behaviour (lower case matches both lower and upper case, but upper case matches only upper case), because that's the really useful option, the best of both worlds. With ignorecase you lose the ability to target more precisely by using uppercase letters, and in the specific case of Lightspeed that is a pretty big tradeoff, because capitals usually mean shortcutable positions, or trigger either the autojump-to-first-match or autojump-to-unique-char feature.

However, I've been thinking about this, and - sadly! - smartcase is an impossible feature for us, by design. This is one small tradeoff of ahead-of-time labeling. A minimal example to illustrate the problem: xy xy xY xy. Let's assume for a moment that "smartcase" is on, I have pressed x, and now Lightspeed has to figure out the labeling, before getting the 2nd input: if I press Y now, I would reach xY directly, that means it should get an "unlabeled" highlight. On the other hand, if I press y, then, as the third among the xy matches, it should get a label s (according to the current defaults). The highlighting cannot be determined unambiguously.

The simpler ignorecase-like behaviour is doable though, but I'm not 100% sure it would worth the effort, because of the above reasons. If it's on, then even if an N is the only one on the screen, all the clever features go out the window (pun intended), if there are lots of ns in-between.

@rodneykeeling
Copy link
Author

Thanks for the link (my searches all included case, sensitive, insensitive, etc and I didn't think to search for ignore) and the thorough explanation. This absolutely makes sense given the design, so I'll just rework my muscle memory to be explicit with case.

Thanks again!

@ggandor
Copy link
Owner

ggandor commented Oct 21, 2021

Linked this issue in the README. Let's close this for now - I doubt that we can cut the Gordian knot and come up with some groundbreaking solution to the "smart case" issue without sacrificing the core of the plugin.

@ggandor ggandor closed this as completed Oct 21, 2021
@ggandor ggandor added the wontfix This will not be worked on label Oct 21, 2021
ggandor added a commit that referenced this issue Nov 5, 2021
The feature is available for f/t motions, and for s/x after auto-jump to
first match or cold-repeat.

API change: new `opts` field `exit_after_idle_msecs`.

Closes #64
@ggandor
Copy link
Owner

ggandor commented Nov 5, 2021

Note: mistakenly referenced this issue in be9e1b4.

@milisims
Copy link

I love this plugin, but I do find the lack of smartcase to be rather painful. I do not think this is impossible by design, either, as the following can be implemented (no idea about difficulty though!) without changing any of the current behavior of the plugin:

If the user has the text: xy xy xY xy xY, similar to your example, and the user types x, then highlight as if ignorecase is on:
xy xys xYf xyn xYu
Now, if the user types Y, the highlights for lowercase are dropped, and the uppercase ones are unmodified:
xy xy xYf xy xYu
and the cursor is on the first match (here, labeled with f). If the user stops for a moment (whatever the default timeout is), the remaining highlights go away, and the cursor is left at the first xY. If the user types f, they are cleared instantly and the cursor stays put.

Whether or not the the lowercase highlights are dropped could easily be the difference between ignorecase and smartcase, and the details of cursor movement are obviously up to you, but I think it's be a really nice improvement.

@ggandor
Copy link
Owner

ggandor commented Nov 18, 2021

@milisims Unfortunately this would not work either, this problem is a bit like engineering a perpetuum mobile 😊 Okay, let's say that we even give the label f a different highlight, saying "need to press me only if using lowercase", but this hack works only for the very first match. What about all the subsequent matches? The thing is, no matter what we come up with, somehow we'd need to show two different possible routes to the user at the same time - because that is the very point of smartcase.

The only (theoretical) solution would be to show two labels next to each other, but there's no point in that, obviously. Or a system with some arcane rule like: "for a label x, the 'uppercase' label is understood to be the next char in the alphabet (y)", where the latter is not shown explicitly, and an accordingly perforated label list is used. 😀

@milisims
Copy link

Okay, let's say that we even give the label f a different highlight, saying "need to press me only if using lowercase", but this hack works only for the very first match. What about all the subsequent matches?

Subsequent matches behave identically to standard behavior regardless of smartcase, and do not need additional labeling. As for the first match of smartcase, I'm of the mind that it doesn't need a different label at all and the cursor moving into place with the uppercase letter-press is communication enough. If lowercase is used then it's already behaving correctly. That said, I can see the argument to highlighting it differently, (knowing ahead of time allows your brain to do the decision while fingers do their thing), I just don't think it's necessary. That would obviously be up to you!

The thing is, no matter what we come up with, somehow we'd need to show two different possible routes to the user at the same time - because that is the very point of smartcase.

The point of smartcase, as a heavy user of it (in vim-sneak as well), is to ignore case most of the time, then occasionally use the uppercase to be more specific when a situation might call for it. Going back to our example, if our target is the second xY, the two possible routes are xyu and xYu. There's no need to show any difference here, that's just what smartcase is. If our target is the first xY, then the routes are xY, xYf, and xyf. The latter two are already handled because we don't need to communicate any additional information, and the first one could be handled as you mentioned, or not as all as I did.

The only (theoretical) solution would be to show two labels next to each other

My recommendation is already able to achieve deterministic behavior with this single-label design, and would be consistent with current and ignorecase behavior (when/if implemented). I'm sure other designs can work as well, but this one seems straightforward and intuitive to me.

@ggandor
Copy link
Owner

ggandor commented Nov 18, 2021

All smartcase does is triggering case-sensitivity - that is, filtering out lowercase matches - when the pattern is uppercase, that's the very definition of it. I'm not sure I understand your suggestion correctly, but basically it's about implementing a smartcase-like feature that only applies to the first match? That is surprising/inconsistent behaviour level 99.

ab aB ... 50 ab matches in-between ... aB

With smartcase (forget about AoT-labeling for now, let's say we're using Sneak), I should be able to reach the second aB by aBs, and so on, not by aB[switch][some-label-with-a-pretty-big-index].

@milisims
Copy link

but basically it's about implementing a smartcase-like feature that only applies to the first match?

This seems to make it clear that we're having a fair bit of miscommunication, because I can not understand how you draw that conclusion. Or I don't understand what you mean. To attempt to clear that up, I'll over-explain here (only in hopes to clear up any misunderstanding, if they exist), and if it doesn't make sense still then well, we tried 😄

Lets say our buffer is:
xy xy xY xy xY

We have smartcase enabled, and start a 2 character search. Starting with an uppercase is trivial (force case sensitive), so I'll ignore it here. Starting with lowercase, x behaves as ignore case so we see
xy xys xYf xyn xYu

We can now press either y or Y. If we press y, we continue behaving like ignorecase, the labels don't change, and the jump behaves as expected for ignorecase. My suggestion for smartcase starts here, when we type Y, we can drop the lowercase labels and jump to the first xY match:
xy xy xYf xy xYu

Because the labels didn't change, the cognitive load hasn't changed, and I don't have an opinion on whether f is highlighted differently to communicate where the cursor might end up. It might change the user experience, but I'm not really interested in that intricacy, just whether smartcase is possible. Not to say it's not an important decision! I just don't have an opinion on it.

That's it, we can jump using AoT calculated labels with smartcase without changing the inherent behavior of the plugin: I look at a place I want to be, start a 2 character search and start typing those characters, and complete the search with whatever label the plugin shows me. That workflow does not change. Intuitively this design (to me, it's necessarily subjective) works similarly to smartcase searching with /.

Now going to your ab example, ab aB ... 50 ab matches in-between ... aB, with smartcase, the second aB would be labeled with the .. 52nd? index. When the user types aB, all of the ab matches are dropped and the second aB remains at whatever the 52nd label is. Which is completely fine: I look at the second aB, start my search, type aB and finish with whatever the label told me to type.
I'm not sure what you mean by [switch] though, the labels never switch, some might just go away. Which, technically I guess isn't really necessary.

The point here is that smartcase is posssible, in response to your comment about it not being possible. If you don't like the design, that's okay! Don't do it! But it's clearly possible. Going through all of this has made it clear to me that ignorecase would do functionally 3/4 of what I care about, so that's still a very helpful improvement.

Anyway, I hope it is clear that I really think this plugin has an extremely cool concept, which is why I'm actually passionate enough to write all this up 😀

@ggandor
Copy link
Owner

ggandor commented Nov 19, 2021

Don't get me wrong, I would be the happiest person if we could solve this, but it seems I don't follow you :) Okay, I'll grab a coffee, and try to absorb this once more... 😃

EDIT:
Yes, we're talking past each other... :) Your suggestion just eliminates some visual noise in the second step, which is fine in itself, but the point of smartcase is to actually be able to reach the match in some easier way (be it autojump triggered, or getting an easier-to-type label). This suggestion would make a difference only in case of the very first match.

I'm not sure what you mean by [switch] though

The group switch key (space, tab, or whatever).

Going through all of this has made it clear to me that ignorecase would do functionally 3/4 of what I care about, so that's still a very helpful improvement.

That is not entirely out of the question (ignorecase), but I'm still skeptical of its usefulness. You cannot know beforehand when it will actually slow you down (because sometimes it will!), so it seems a bit weird to me to commit to it exclusively.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants