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

Already on GitHub? Sign in to your account

Add vim's ]p and ]P commands #896

Open
wants to merge 3 commits into
from

Conversation

Projects
None yet
3 participants

]p and ]P behave just like p and P, except that they re-indent the text that was pasted so it has the same indentation as the surrounding code.

Lots of credits to https://reddit.com/u/VanLaser

Known issues:

  • The return value isn't correct. The commands should return the yanked text (to be consistent with evil-paste-after and evil-paste-before) but I couldn't figure out how to do this, as the the call to evil-paste-* isn't the last function call in the function body...
  • There are no tests.
Add vim's ]p and ]P commands
`]p` and `]P` behave just like `p` and `P`, except that they re-indent
the text that was pasted so it has the same indentation as the
surrounding code.

Lots of credits to https://reddit.com/u/VanLaser
Member

wasamasa commented Aug 8, 2017

  • Either bind the desired return value within let and put the variable as last form in the body or use prog1 (which is somewhat less readable and harder to refactor)
  • I'd definitely want tests before merging this, check out the existing tests and the evil-test-buffer macro
  • Where are the corresponding commands for [p and [P?
  • I'll look into it and I'll try using let.
  • I've checked out the tests but I've never worked with (e)lisp before last night, and I couldn't really figure out what is happening... I'll check them out again and try to come up with something.
  • I didn't even know those existed! They do the same thing as ]P though, so that's trivial to add.

I hope I can find some time tonight to update.

VanLaser commented Aug 8, 2017 edited

@olmokramer - sorry for putting you to such trouble! I'm not very familiar with github's workflow, nor with evil's tests (if I understand well, one could inspire from something like this test: evil-test-paste-after - maybe just a few, well-chosen, more difficult tests would do? E.g. pasting directly with a count, from a register)

BTW, in the quest for perfection, there's also another improvement that can be made - after a paste operation, C-p in normal mode selects the previous text from the yank-ring (see evil-paste-pop); I think the paste&indent commands should also be registered with C-p. One way is to modify evil-paste-pop (memq ...) - another would be to fake it into believing that a paste&indent operation was one of the paste operations it already knows of. Something like this perhaps:

(evil-define-command evil-paste-before-and-indent (count &optional register yank-handler)
  "Paste text with `evil-paste-before' and re-indent it so it has the
same indentation as the surrounding code."
  :supress-operator t
  (interactive "P<x>")
  (evil-with-single-undo
    (let ((text (evil-paste-before count register yank-handler)))
      (evil-indent (evil-get-marker ?\[) (evil-get-marker ?\]))
      (setq this-command #'evil-paste-before) ;; for `evil-paste-pop' (C-p)
      text)))

... although, ideally, C-p after a paste&indent command should also indent!

Maybe if evil-paste operations would look at some internally-used evil-indent variable and conditionally indent the code after pasting, internally. Operations such as ]p would set the option to true, C-p would make use it, while most other paste operations (e.g. p) would reset it. So, unlike now, indentation would be handled internally by the already existing paste operations.

@VanLaser That's alright, it's a good learning experience :) And thanks for yet another tip about C-p, that's nice to know. I won't have time tonight to work on it anymore, maybe tomorrow. If you want to take another stab at it, though, then, by all means... :) Thanks again!

olmokramer commented Aug 9, 2017 edited

Ok, I've been trying to work on this for a few hours but made 0 progress (except that I found a bug in evil-paste-{after,before} -- their return value is not what the docstring says when doing a line-wise paste -- but I've opened a separate issue for that: #898) and I'm starting to think that this is way over my head right now. I haven't even been able to figure out what evil-paste-pop exactly does (it seems to replace my just-pasted text with the same text, but I can't see how that's useful) and its docstring/code isn't clarifying a lot (at least to me...) Maybe when I'm a bit more comfortable with Emacs I can continue to work on this, but until then, if anyone wants to pick this up, please feel free to use the work so far and don't forget to credit @VanLaser (really, he did most of the work :).)

Member

wasamasa commented Aug 10, 2017

The return value is a string, (stringp (evil-paste-after)) will return t.

I'll keep this one open as the only thing missing here are tests. Figuring them out takes like half an hour of staring at existing tests and another to write and test your own.

olmokramer commented Aug 10, 2017 edited

Well, wouldn't you want C-p to work with this? I can implement @VanLaser's workaround ((setq this-command #'evil-paste-{after,before})) but, like they said, then the C-p text won't be re-indented.

Then there's another issue: in vim, if the pasted text spans less than a line (e.g. yiw), the cursor will be at the end of the pasted text after the paste, while if the text spans an entire or multiple lines (e.g. yy or yj or <C-v>jly) the cursor will be at the beginning of the pasted text. I can use evil-goto-mark to do this at the end of the command, but how do I select which one to use? Is there an internal variable that I can check? I can't just go comparing initial and final line numbers because of the <C-v>...y case, which doesn't paste all text on a new line, but does span multiple lines so the cursor goes to the start of the pasted block. I also can't compare the line numbers of the ?[ and ?] marks, because they will be the same for yyp.

EDIT: I got close to solving the problem by storing (point) after the paste and then going back to that position with goto-char, but it's off by a few characters if the text was re-indented.

I did figure out the tests, they are quite easy indeed. I'll await your response and then write some if this is going to be mergeerd.

VanLaser commented Aug 10, 2017 edited

Because string properties just got discussed, in relation to that returned text, I just got the idea and checked, so ... if I'm not mistaken, the yanked text itself stores the type of the fetching operation (line, block or none - aka char) used when it got copied (and this is the text evil-paste-... returns). While the yank-handler function argument is always nil, so it can't be used directly. E.g.:

(evil-define-command evil-paste-after-and-indent (count &optional register yank-handler)
  "Paste text with `evil-paste-after' and re-indent it so it has the
same indentation as the surrounding code."
  :supress-operator t
  (interactive "P<x>")
  (evil-with-single-undo
    (let* ((text (evil-paste-after count register yank-handler))
	   (paste-type (when (stringp text)
			 (car-safe (get-text-property 0 'yank-handler text)))))
      ;; paste-type -> evil-yank-line-handler, evil-yank-block-handler or nil
      (message "%s" paste-type)
      (evil-indent (evil-get-marker ?\[) (evil-get-marker ?\]))
      (setq this-command #'evil-paste-after) ;; for `evil-paste-pop' (C-p)
      text)))

So, in this case, one could place the cursor differently, based on the value of paste-type.

BTW, small thing, I suggest using ?\[ and ?\] since the \ is harmless in this case (says the manual), but without it, the syntax highlighting and some "pair matching" add-on modes can get tripped when walking over the naked ?[ ?].

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment