Open tremby opened 7 years ago
I agree to this. We should spec out what will be needed, such as movements, countables, text objects. If we have a complete specification then it will be easier to design and implement, and no refactoring needed in the future.
Just some thoughts while they bubble up. This is pretty stream-of-consciousness, so sorry if it's too rambling.
:he object-select
)Vim's text object selections (v)a"
et all and (v)i"
and that sort of thing select "a
round" or "i
nside" the object. That generally means whether or not surrounding whitespace is included, or whether the wrapping quotes/brackets are included. In the context of PMS I don't think these are any different since there's no brackets or whitespace, and an album or artist is self-contained. As such, any implementation of something like text object selection should have duplicate default bindings of the a
and i
forms.
t
and f
(:he f
)Vim's t
/T
and f
/F
go unt
il before or f
orward to the next/previous instance of the literal typed after. We don't have literals like this, except maybe search results. I don't think it would be useful to go forward to the next instance of a song with a letter c
in any tag, for example... But I'll come back to this.
(There are also ;
and ,
to repeat these motions.)
:he word-motions
, :he object-motions
)Vim's w
/W
/b
/B
/e
/E
/ge
/gE
and (
/)
/{
/}
/[[
/[]
/]]
/][
are motions with forwards and backwards versions for various objects.
Some possible mnemonic equivalents to "word motions":
b
to mean alb
uma
to mean a
rtisty
to mean y
eard
to mean d
isc/side (of an album)I can't think of other logical objects which would be useful. Maybe genres? I don't use genres so can't comment.
Alternatively, a
for a
lbum and r
for ar
tist? ("R"-tist...?)
Having lowercase bindings for each of these means uppercase versions are possible and intuitive for the backwards variants of the motions.
Unfortunately, there are a lot of collisions with useful and intuitive existing bindings here, which already align nicely with vim, such as a
ppend and y
ank (and I'd like to implement d
elete).
Another approach with no such collisions would be to do something similar to the object motions instead:
)
/(
for disc (vim: sentence)]
/[
for album (vim: unused alone, but as [[
/[]
/]]
/][
to do with braced blocks of code)}
/{
for artist (vim: paragraph)No suggestion for year here. I'm not convinced moving through music by year is useful (to me at least), anyway.
Vim has no single [
or ]
motion (they're [[
, []
, ]]
, and ][
and I haven't thought through the usefulness of using things like those to go to the beginning/end of an object) so they could be repurposed as suggested above for single keystrokes (not requiring shift) for album.
Another suggestion is what the current defaults hint a little at already, but I suggest changing them.
Currently b
goes to the start of the current album, or if already there, the start of the previous album. This makes sense to me. As you know of course, the behaviour in vim is very similar but for a word: in vim this goes to the start of the current word, or if already there, the start of the previous word. So far so good.
Currently e
goes to the start of the next album. This is different. In vim, e
goes to the end of the current word, or if already there, to the end of the next word. Not the beginning of a word in any case (unless it's a one-character word, I guess). This is the equivalent of vim's w
, not e
.
I suggest instead:
b
stays as is (start of album, or previous album if already there)w
goes to the start of the next album (does what e
does at present)e
goes to the end of the current album (or end of next if already there)ge
goes to the end of the previous album (in vim this goes to the end of the previous word. It's two keystrokes rather than one but it's used a lot less frequently in my experience, so this is no issue.)This leaves the uppercase versions free to use for artists:
B
goes to the start of the current artist (or previous if already there)W
goes to the start of the next artistE
goes to the end of the current artist (or end of next if already there)gE
goes to the end of the previous artistPerhaps it then makes sense to add in these, which I'm realizing for the first time are similar in vim to the previous but for top-level functions/blocks/classes when using a suitable coding style like Allman (BSD) or Horstmann (I don't, so have never used these in vim).
[[
goes to the start of the current disc (or previous if already there)]]
goes to the start of the next disc][
goes to the end of the current disc (or end of next if already there)[]
goes to the end of the previous discThis would also free up (
, )
, {
, }
for other things. They could for example be aliases for next/prev album and artist (for b
, w
, B
, W
respectively, in this suggested scheme).
Coming back to t
and f
: there could potentially be intuitive default bindings for people used to these (assuming the suggestions like "alb
um" above):
tb
would act like e
fb
is w
Tb
is b
Fb
is gE
...acting on alb
ums, then the same for a
rtists (ta
, fa
, Ta
, Fa
), d
iscs, and maybe y
ears.
The same letters could also be used for text object selection, so for example vaa
or via
would select the entire current artist, dab
or dib
would delete the current album, and so on.
Alternatively, scrap this second letter scheme and use the letters borrowed from vim for motions, so w
is an album, W
is an artist. I'm not sure what would be best for disc in this case. Possibly [
/]
to match the keybindings, possibly {
/}
/b
, which are "braced block" in vim, and are similar to what the [[
etc keybindings move between. (b
is colliding here. Unless we were to go "ar
tist" and "a
lbum" rather than "a
rtist and "alb
um".)
I intend at the very least to give you a pull request to implement b
/w
/e
/ge
and their uppercase variants as suggested here, as well as [[
etc.
It'll involve adding two new commands similar to prevOf
and nextOf
, and possibly reworking/renaming prevOf
...
Currently prevOf
goes to the start of the current, or start of previous if already there. It should probably be called startOf
instead? The full complement might be
startOf
(like b
)endOf
(like the suggested e
)startOfNext
(like w
)endOfPrev
(like ge
)Thoughts, on this, and the rest?
Just a heads up: I edited the above comment a couple of times, so read on Github rather than in the email.
Thank you for this thorough discussion of movement verbs.
The way I see it, the three most useful tags when it comes to movement are albumartist
(not artist
, because of VA releases), album
, and disc
. It could be beneficial to do as you suggest in the third section: drop the mnemonics and stay closer to the Vim-feel of b
, e
, w
, ge
and their uppercase variants. This facilitates quick movement and would feel natural. But how about "text object" selection support in this case? dib
would delete the currently selected alb
um. If that means that die
would do the same thing, we keep the mnemonic feel of it.
I'm curious about how the data model for keyboard input would look. Right now, there is no concept of countables nor movement, only direct binding to a specific command. I propose that we take a look of how we could represent a text input sequence in memory before deciding.
Lastly, I fully support your planned pull request. It adds some of this functionality without losing sight of the big picture.
albumartist
(notartist
, because of VA releases)
Absolutely. When I used the word "artist" I was taking a more high-level perspective. Yes, the implementation should certainly use albumartist
or, in my view, musicbrainz_albumartistid albumartist
.
I'm using that -- nextOf musicbrainz_albumartistid albumartist
etc -- in my commands, and it works great. Ever since I got some rereleases and instrumental versions and that sort of thing I kept getting annoyed that adding the album would also add the rerelease if it was right after in the list. The same could happen for two artists with the same name even if they're actually different artists. I don't actually have any of these, but it's a totally plausible case. Hence it's good to use the ID tags for album and artist and albumartist if they're present.
But how about "text object" selection support in this case?
dib
would delete the currently selected alb
um. If that means thatdie
would do the same thing, we keep the mnemonic feel of it.
This is tricky. So if we start with just one example, diw
, it seems okay -- it works the same way vim does. w
moves forward a word in vim, or an album here, so an album is our "word". diw
deletes a word in vim, and so could delete an album in PMS. So far so good. But then b
goes back a word. But dib
in vim isn't an alias for diw
. In fact, it deletes everything inside a set of parentheses, which is a totally different thing. Similarly, there are no die
or dige
. So I don't think we should set up these aliases.
I think vim only has diw
and w
acting on the same sort of object as a coincidence, because w
happens to be one of the mnemonic bindings. In contrast, dip
deletes in a paragraph, but p
doesn't move to the next paragraph.
What I'm getting at here is that the command b
would have something to do with an album (move back one), but that's not why it's bound to b
. It's bound to b
because b
ackwards. And then e
because e
nd.
So I think I've made up my mind that the two separate schemes are the way to go. The w
, e
, b
, ge
and W
, E
, B
, gE
bindings are totally separate from the bindings to operate on text objects. These movements aren't mnemonic, they're just plain vim-isms. Then the text objects, as I suggested, could be mnemonic: b
for album, a
for artist, d
for disc. dib
, dia
, did
would exist, but no diw
, diW
-- no connection to the movements.
Seem OK to you?
I'm curious about how the data model for keyboard input would look.
No idea!
I think the first step could be to get modal bindings implemented. For example, we'd need ib
when in visual mode to move the selection to cover the current album, but it shouldn't do this in normal mode. That'd give us somewhere to hack on these "text objects" without the need (yet) of a full operator implementation (for dib
and the like).
Lastly, I fully support your planned pull request. It adds some of this functionality without losing sight of the big picture.
Great. I'll do this at some point soon.
w
, b
, e
, ge
, W
, B
, E
, gE
, ]]
, [[
, ][
, []
startOf
(likeb
)endOf
(like the suggestede
)startOfNext
(likew
)endOfPrev
(likege
)
Starting to doubt these names now. Help! I think startOfNext
describes w
well, and endOfPrev
describes ge
well, but I'm not sure about startOf
and endOf
any more, since they don't adequately describe what happens when you're already at the start of the current album in the b
case or the end of the current album in the e
case. Changing them to startOfPrev
and endOfNext
then doesn't describe them accurately either.
The only things I can think of which are more accurate are kind of long, like backStartOf
for b
and forwardEndOf
for e
...
backStartOf
, fwdEndOf
, fwdStartOf
, backEndOf
for more consistency and to be shorter?
Suggest backwardStartOf
, forwardStartOf
, backwardEndOf
, forwardEndOf
? I'm not particularly keen on abbreviations, because it makes things harder to read, and we do have tab completion.
I tried to work a bit on the data model yesterday, here's what I have at the moment. It's not ready for a pull request yet. Just trying to dump what's in my head for further discussion.
The thought here is that every time the user presses a key, the statement is updated accordingly. If there is an invalid sequence (e.g., count
is attempted twice, or there are two operators) the statement is reset and the terminal bell is rung.
Using this approach it's possible to remove many of the explicit bindings. I guess that in order to support this way of interpreting the input, we would either have to hard-code the motion keys, or provide contextual bindings. So, for instance:
bind movement j down
bind operator x remove
bind movement e forwardEndOf albumartist album
I think the actual cursor
command would have to be refactored away.
// Statement translates command mode keystrokes to motions and operations.
type Statement struct {
AbsoluteIndex int // Absolute line number.
Position int // Beginning, end, inside, around, percentage.
Count int // The amount of lines spanned.
Direction int // -1 for up, 1 for down. All movement is one-dimensional.
Operator commands.Command // The actual command to run.
Range [2]int // FIXME: support ranges?
Tag string // FIXME: one tag or several?
}
Suggest
backwardStartOf
,forwardStartOf
,backwardEndOf
,forwardEndOf
? I'm not particularly keen on abbreviations, because it makes things harder to read, and we do have tab completion.
OK.
It'd help my understanding a lot if you gave a couple of examples which used the various pieces of the Statement
struct you propose in different ways.
Quick nitpick on nomenclature: "operators" need a visual selection or movement or text object, "commands" run immediately. (:he visual-operators
). x
wouldn't be an operator but a command. (Today I learned when looking at the vim manual about X
, which does the same for one character behind the cursor. Surprised I'd never seen it.) Meanwhile, d
is an operator.
I'm also going to suggest "motion" rather than "movement" -- they appear to mean the same in the vim documentation but "motion" seems to be preferred. I'm going to edit the title of this issue. "motions" are commands, but can also be used on a pending operator. (:he motion
)
Motion bindings take effect in normal or visual modes or when an operator is pending. They can be preceded by a count.
If used in normal or visual modes, the cursor is moved according to the motion.
If used when an operator is pending, the range to be operated on is the range starting with the cursor's position before the movement and the cursor's position after the movement. After operation, the cursor ends up at the top of the range.
Something which comes to mind is that motions in PMS would all be "inclusive". (:he inclusive
). "Linewise motions always include the start and end position."
I wonder if these should be bound a few times (:nbind
, :vbind
, :obind
-- may end up with simpler code?), or just once (:mbind
for motion bind, or :bind motion
or similar).
mbind j down
mbind k up
mbind e forwardEndOf musicbrainz_albumid album
Objects can be entered in visual mode to move the selection to the object, or when an operator is pending. Objects start (by convention I guess?) with a and i.
Setting them up is similar to vim's :omap
. Should it be :obind
? :bind object
?
obind ab nearby musicbrainz_albumid album # Is "nearby" necessary? Are there any objects which would *not* be defined as nearby tags?
obind ib nearby musicbrainz_albumid album
obind aa nearby musicbrainz_albumartistid albumartist
obind ia nearby musicbrainz_albumartistid albumartist
Operators expect a visual selection (and then operate immediately), or to be followed by a motion or an object.
vbind d delete
nbind d delete
Some things I haven't through through too much yet:
dd
from normal mode? d
is an operator expecting a motion or object. Entering another d
should cause it to act like x
(delete the current track), but yd
shouldn't yank the current line and dy
shouldn't delete the current line. Is d
a special case motion for the d
operator? Or is dd
a totally separate binding from normal mode, which is the same as x
? I think it can't be separate, or we wouldn't be able to do d3d
, which works the same way as 3dd
or d2j
.a
but then the object ab
is an album? Will dab
delete to the next artist and then go back one album, or will it delete one album?
It'd be great to have a further-implemented "delete" verb (this is the main one I can think of; add would be useful too), and movements for it to act over.
For example dG should delete to the end of the current list, dgg should delete to the beginning of the current list, d] should delete to the next album (assuming ] is set up as such).
Perhaps getting ahead of myself here but an equivalent of vim's text objects would be great too, like daa (or dia) might delete everything surrounding the cursor with the same album, daA (or diA) might work the same way but for an artist, and so on.