4

I'd like to modify the behavior of kill-sentence (which I have bound to M-k) according to context, in the following way:

  1. If the point is at the beginning of a sentence, then kill the sentence in the usual way, deleting all the words of the sentence, as well as the period and the space after the period.
  2. If the point is in the middle of a sentence somewhere, it makes no sense to delete the period and space, because this unwantedly joins two sentences. So if point is inside a sentence, I want Emacs to run kill-sentence-to-period, below, so as to preserve the period.

i.e. something like this:

(defun kill-sentence-to-period ()
    "Kill the rest of the sentence but leave the period."
    (interactive)
    (kill-sentence)
    (push-mark)
    (insert ".")
    (backward-char))

Is it possible to change the behavior of M-k depending on the context, conditional on whether or not the point is inside a sentence?

verdammelt
  • 367
  • 3
  • 10
incandescentman
  • 4,111
  • 16
  • 53

2 Answers2

2

Here's an attempt at defining a custom kill-sentence-dwim command that will either kill the entire sentence or kill up to the sentence-ending punctuation.

(defun my/forward-to-sentence-end ()
  "Move point to just before the end of the current sentence."
  (forward-sentence)
  (backward-char)
  (unless (looking-back "[[:alnum:]]")
    (backward-char)))

(defun my/beginning-of-sentence-p ()
  "Return  t if point is at the beginning of a sentence."
  (let ((start (point))
        (beg (save-excursion (forward-sentence) (forward-sentence -1))))
    (eq start beg)))

(defun my/kill-sentence-dwim ()
  "Kill the current sentence up to and possibly including the punctuation.
When point is at the beginning of a sentence, kill the entire
sentence. Otherwise kill forward but preserve any punctuation at the sentence end."
  (interactive)
  (if (my/beginning-of-sentence-p)
      (progn
        (kill-sentence)
        (just-one-space)
        (when (looking-back "^[[:space:]]+") (delete-horizontal-space)))
      (kill-region (point) (progn (my/forward-to-sentence-end) (point)))
      (just-one-space 0)))

You can bind my/kill-sentence-dwim to a key binding of your choice. If you want to replace the existing kill-sentence binding you could use this:

(define-key (current-global-map) [remap kill-sentence] 'my/kill-sentence-dwim)

A couple notes:

  • Instead of just assuming a period, my/forward-to-sentence-end moves to the end of the sentence and then backs up to just after the last alphanumeric character. That should preserve any closing punctuation including any quotes or parens.

  • I'm checking whether point is at the beginning of the sentence by calling forward-sentence to jump to the end and then back to the beginning, then seeing if point changed. Not sure how reliable this will be but it seems correct in the simple cases I tried.

  • As requested in the comments, this kill command also attempts to fix up whitespace. When deleting a partial sentence it removes whitespace before the ending punctuation. When deleting an entire sentence it leaves one space between sentences or no spaces at the beginning of the line. There will likely still be edge cases...

Scott Weldon
  • 2,695
  • 1
  • 17
  • 31
glucas
  • 20,175
  • 1
  • 51
  • 83
  • This works! This should be the canonical behavior, since again, the default behavior (joining two sentences if I invoke kill-sentence from midsentence) makes no sense. – incandescentman May 11 '15 at 19:50
  • In many cases, depending on where I am in a sentence when I invoke my/kill-sentence-dwim, I'm left with a space before the period, `like this .` How hard would it be to add code to check for an extra space between a character and the period, and then automatically delete it? I realize this is more complicated. I can't think of any case where I would want a space before the period at the end of a sentence. – incandescentman May 11 '15 at 19:52
  • 1
    I've updated the code to remove spaces at the end of the sentence using `just-one-space`. – glucas May 11 '15 at 20:12
  • That works! Last question: When killing an entire sentence from the beginning, it still leaves an extra space. Possible to remove that extra space too? – incandescentman May 11 '15 at 21:36
  • 1
    You can use `just-one-space` again. So if you want to leave one space between sentences you could replace `(kill-sentence)` in the code above with `(progn (kill-sentence) (just-one-space 1))`. – glucas May 11 '15 at 21:41
  • 1
    For the record, you can also just call `just-one-space` directly. It is bound to `M-SPC` by default, so you would probably get the behavior you want by hitting `M-k` followed by `M-SPC`. – glucas May 11 '15 at 21:42
  • Thanks. I tried that and it still leaves an extra space. Case: I'm at the beginning of a line, at the beginning of a sentence, point on the first character of the sentence. I invoke `my/kill-sentence-dwim`. It leaves a space at the beginning of the line before the sentence begins. – incandescentman May 12 '15 at 13:08
  • 1
    Oh, right -- `just-one-space` will in fact always leave a space. I've fixed it by checking specifically for space at the beginning of the line, but we may be reaching the point of diminishing returns in tweaking this answer. If you run in to more cases it may be a good opportunity to experiment with elisp. Good luck and hope this has been helpful! – glucas May 12 '15 at 13:44
  • Nope, that solves it. Perfect! – incandescentman May 12 '15 at 17:19
  • I did as you suggested and I've been experimenting with elisp. I'm trying to create a function so that I can highlight a region (select text) and then hit BACKSPACE and Emacs will automatically (1) delete region, and (2) make sure there's only one space, and no extra space at the beginning of the line. I tried [a couple different ways](http://pastebin.com/RCBiv9ia) but no luck yet. Thoughts? – incandescentman May 14 '15 at 16:20
  • 1
    @PeterSalazar Since we're straying from the original question, let's move this discussion to a chat: http://chat.stackexchange.com/rooms/23774/discussion-between-glucas-and-peter-salazar – glucas May 14 '15 at 16:30
  • @glucus I'm trying to reach the chat link you sent to but it seems to have been removed? – incandescentman May 26 '15 at 01:41
  • I commented on your gist instead. – incandescentman May 26 '15 at 01:50
1

A quick-and-dirty (i.e., too simplistic) answer is this:

(defun at-sentence-beginning-p ()
  "Return non-nil if at the beginning of a sentence."
  (looking-back (sentence-end)))

It really tests whether point is after a sentence end. There are plenty of cases where it does not do the right thing, including, for example, a sentence enclosed in parens. But it might help you get started.

Drew
  • 75,699
  • 9
  • 109
  • 225
  • Great, and how can I make it so that `kill-sentence` uses that information to do the right thing based on context, i.e. to do one or the other of the two cases above? – incandescentman May 09 '15 at 05:31
  • I don't understand; what two cases? What I was trying to say is that "there are plenty of cases" where it does not do the right thing. You would likely need to experiment and see what happens in different contexts. And then special-case some of the contexts where it doesn't do what you want. Looking back at sentence-end is likely good for whenever it returns non-nil (but maybe not always?), but when it returns nil you might still be at a sentence start (e.g. at `bobp`). Experiment on real text that you might care about, to see where it fails. – Drew May 09 '15 at 06:08
  • @PeterSalazar I think what you're asking is how to change the behavior of kill-sentence? You don't. What Drew is suggesting is you need to rebind the key to your own custom command that uses looking-back to decide whether to call kill-sentence or do something else. – glucas May 10 '15 at 11:22
  • @glucas Exactly. How should I rephrase the question to reflect that? As you said, what I need is to create a new function that (1) uses `looking-back` to determine the position of the point and (2) decides whether to call `kill-sentence` or `kill-sentence-to-period` (3) calls one or the other. – incandescentman May 10 '15 at 18:44