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

Implement motions and operators #92

Open
tremby opened this issue Aug 30, 2017 · 11 comments
Open

Implement motions and operators #92

tremby opened this issue Aug 30, 2017 · 11 comments

Comments

@tremby
Copy link
Collaborator

tremby commented Aug 30, 2017

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.

@kimtore
Copy link
Owner

kimtore commented Sep 1, 2017

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.

@tremby
Copy link
Collaborator Author

tremby commented Sep 8, 2017

Just some thoughts while they bubble up. This is pretty stream-of-consciousness, so sorry if it's too rambling.

On parallels to vim

Text object selection (:he object-select)

Vim's text object selections (v)a" et all and (v)i" and that sort of thing select "around" or "inside" 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 until before or forward 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.)

Word and object 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.

Suggestions

Mnemonic word motions

Some possible mnemonic equivalents to "word motions":

  • b to mean album
  • a to mean artist
  • y to mean year
  • d to mean disc/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 album and r for artist? ("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 append and yank (and I'd like to implement delete).

Object motions instead

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.

Or (or additionally) drop the mnemonics and be closer to vim

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 artist
  • E goes to the end of the current artist (or end of next if already there)
  • gE goes to the end of the previous artist

Perhaps 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 disc

This 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 "album" above):

  • tb would act like e
  • fb is w
  • Tb is b
  • Fb is gE

...acting on albums, then the same for artists (ta, fa, Ta, Fa), discs, and maybe years.

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 "artist" and "album" rather than "artist and "album".)

For now

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?

@tremby
Copy link
Collaborator Author

tremby commented Sep 8, 2017

Just a heads up: I edited the above comment a couple of times, so read on Github rather than in the email.

@kimtore
Copy link
Owner

kimtore commented Sep 9, 2017

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 album. 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.

@tremby
Copy link
Collaborator Author

tremby commented Sep 10, 2017

albumartist (not artist, 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 album. If that means that die 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 backwards. And then e because end.

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.

@tremby
Copy link
Collaborator Author

tremby commented Sep 10, 2017

  • Adjust the movement-by-tag commands
  • Add default bindings w, b, e, ge, W, B, E, gE, ]], [[, ][, []

@tremby
Copy link
Collaborator Author

tremby commented Sep 10, 2017

  • startOf (like b)
  • endOf (like the suggested e)
  • startOfNext (like w)
  • endOfPrev (like ge)

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?

@kimtore
Copy link
Owner

kimtore commented Sep 10, 2017

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.

@kimtore
Copy link
Owner

kimtore commented Sep 10, 2017

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?
}

@tremby
Copy link
Collaborator Author

tremby commented Sep 10, 2017

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.

@tremby
Copy link
Collaborator Author

tremby commented Sep 10, 2017

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)

Motions

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

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

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:

  • How do we define things like 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.
  • What if bindings have conflicts, like a motion to go to the next artist is bound to 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?
  • What if someone binds something to numbers? Will this mess up counts completely?
  • Would it make more sense to go with vim's approach of binding things in a more hardcoded way, and allowing mapping?

@tremby tremby changed the title Implement movements and verbs Implement motions and operators Sep 10, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants