Tyring to create a duplicate-line function in ELisp - emacs

As part of learning ELisp, I am trying to make a function that makes a copy of the current line below the current line (duplicates the line). What I have so far works pretty well, except on the last line of the buffer. If on the last line, then the line just gets pasted at the end of the line rather than below it.
Here is my code:
(defun duplicate-line ()
"duplicate the current line"
(interactive)
(save-excursion
(kill-ring-save (line-beginning-position) (line-beginning-position 2))
(goto-char (line-beginning-position 2)) ; goto the start of the next line
(yank)
)
(next-line)
)
Is there a better way of doing this? I would also appreciate any other advice concerning writing elisp.

I guess this happens only when the last line doesn't end with a newline character.
The following function inserts a newline if necessary, and avoids using the kill-ring.
(defun duplicate-line ()
(interactive)
(let* ((pos-end (line-beginning-position 2))
(line (buffer-substring (line-beginning-position) pos-end)))
(goto-char pos-end)
(unless (bolp) (newline))
(save-excursion ;; leave point before the duplicate line
(insert line))))

You could do instead:
(kill-whole-line)
(yank)
(yank)
to the same effect. But, perhaps, if I was to write such a function, I'd rather make it so the line isn't copied to the kill-ring. Usually, if I need to duplicate line, I don't want it to be there.

Related

Elisp - Function stopping when error is met

I have the following snippet of code:
(defun my-move-line-below ()
(interactive)
(let ((point-from-start (- (point) (line-beginning-position))))
(kill-whole-line)
(next-line)
(move-beginning-of-line nil)
(yank)
(previous-line)
(goto-char (+ (line-beginning-position) point-from-start))))
What it does is it moves the current line below the the next one. It works well, but problem is that when EOF is reached, the function stops after next-line, thus killing the line without yanking it back.
I know I could solve this by checking the line number and such, but I wonder if it is possible to just ignore the error and continue the execution of the function.
I think replacing (next-line) with
(ignore-errors (next-line))
should do the trick.
Just to point out, there is a ready-made function for that, C-x C-t, although it does not remember the point position after transposition.

Duplicating a line in emacs with Ace-Jump

I'm fairly new to elisp, but one thing that I really want to figure out is either how to wait for ace-jump to end before executing instructions or how get a position from ace-jump instead of moving my cursor. My goal is to be able to select a line with ace-jump, copy it, then paste it right above my current line. I started by first trying to go to a line with ace-jump then duplicate it in place, but that hasn't worked. Here is what I have for that:
(defun ace-jump-yank-line-above ()
(interactive)
(ace-jump-line-mode)
(kill-ring-save (line-beginning-position) (line-beginning-position 2) )
(yank)
)
But this gives me strange behavior
You can have a look at the source of my project lispy.el.
It's got several functions that use ace-jump-mode and do something after.
For instance lispy-ace-symbol will ace-jump to symbol and mark it.
Here's the implementation detail - the key is setting ace-jump-mode-hook:
(defun lispy--ace-do (x bnd &optional filter func no-narrow)
"Use `ace-jump-do' to X within BND when FILTER return t.
When FUNC is not nil, call it after a successful move.
When NO-NARROW is not nil, don't narrow to BND."
(require 'ace-jump-mode)
(lispy--recenter-bounds bnd)
(unless no-narrow
(narrow-to-region (car bnd) (cdr bnd)))
(when func
(setq ace-jump-mode-end-hook
(list `(lambda()
(setq ace-jump-mode-end-hook)
(,func)))))
(let ((ace-jump-mode-scope 'window)
(ace-jump-search-filter filter))
(ace-jump-do x))
(widen))
I use something similar to ace-jump rather than ace-jump itself, but something like this should work (can't be sure about the call to ace-jump-line-mode):
(defun ace-jump-yank-line-above ()
(interactive)
(let ((loc (point-at-bol))
(line nil))
(save-excursion
(ace-jump-line-mode)
(setq line (buffer-substring-no-properties
(point-at-bol) (point-at-eol)))
(goto-char (1- loc))
(if (bobp)
(insert (concat line "\n"))
(insert (concat "\n" line))))))
Okay, none of these worked for me, but I used these answers to create a script that works. Here is the code that I used:
;; The base function for the line-based ones
(defun ace-jump-end-do (dfunc afunc)
;; Save where to return to as a marker
(setq ace-jump-do-retpos (make-marker))
(set-marker ace-jump-do-retpos (point))
;; Add our during function to the hook
(setq ace-jump-mode-end-hook
(list `(lambda()
(progn
(setq ace-jump-mode-end-hook)
(,dfunc)
(goto-char ace-jump-do-retpos)
(set-marker ace-jump-do-retpos nil)
(,afunc)
))))
(ace-jump-line-mode)
)
;; Copy the line above the current line
(defun ace-jump-yank-line-above ()
(interactive)
(ace-jump-end-do
;; At the line
(lambda ()
;; Store the line in a variable
(setq line (buffer-substring-no-properties (point-at-bol) (point-at-eol)))
)
;; Upon returning
(lambda ()
(save-excursion
(goto-char (point-at-bol))
(insert (concat line "\n"))
)
(when (bolp) (goto-char (point-at-bol 2)))
)))
Unfortunately, this resets the end hook of ace-jump every time it's called. It works for me though since I don't have anything else hooked to it. If I run into issues, I'll need to figure something else out.
ace-jump-mode is really silly... calling it just goes into some useless minor-mode where you pick the hints, but it is non-blocking: any code afterwards is executed immediately.
There is so much potential for this kind of interaction and ace-jump-mode completely wastes it away with crazy implementation. It also doesn't work at all with save-excursion and you wound need to hack around that with various hooks and state-saving variables.
I've written a new package addressing all these issues, you can find it at https://github.com/Fuco1/better-jump Hopefully people will pick it up, but it serves me well at least. Took me about 2 hours to write the basic working prototype and it already covers all the functionality of packages like ace-link, ace-window and ace-whatever-else-you-can-find (also ace-jump, obviously :))

'Semantic' movement across a line

Consider the following line of Lisp code:
(some-function 7 8 | 9) ;; some comment. note the extra indentation
The point is placed between '8' and '9'. If I perform (move-beginning-of-line), the point will be placed at the absolute beginning of the line, rather than at '('.
Same for move-end-of-line: I'd find it more desirable for it to place the point at ')' if I perform it once, and at the absolute end of the line if I perform it a second time. Some IDEs behave like that.
I tried to implement this but got stuck, my solution behaves particularly bad near the end of a buffer, and on the minibuffer as well. Is there a library that provides this functionality?
I don't know of any library, but it can be done in a few lines of Elisp.
For the beginning of line part, the bundled functions beginning-of-line-text and back-to-indentation (M-m) move to the beginning of the “interesting” part of the line. back-to-indentation ignores only whitespace whereas beginning-of-line-text skips over the fill prefix (in a programming language, this is typically the comment marker, if in a comment). See Smart home in Emacs for how to flip between the beginning of the actual and logical line.
For the end of line part, the following function implements what you're describing. The function end-of-line-code moves to the end of the line, except for trailing whitespace and an optional trailing comment. The function end-of-line-or-code does this, except that if the point was already at the target position, or if the line only contains whitespace and a comment, the point moves to the end of the actual line.
(defun end-of-line-code ()
(interactive "^")
(save-match-data
(let* ((bolpos (progn (beginning-of-line) (point)))
(eolpos (progn (end-of-line) (point))))
(if (comment-search-backward bolpos t)
(search-backward-regexp comment-start-skip bolpos 'noerror))
(skip-syntax-backward " " bolpos))))
(defun end-of-line-or-code ()
(interactive "^")
(let ((here (point)))
(end-of-line-code)
(if (or (= here (point))
(bolp))
(end-of-line))))
Some suggestions that almost do what you ask:
In lisp code, you can sort-of do what you want, with the sexp movement commands. To get to the beginning of the expression from somewhere in the middle, use backward-up-list, which is bound to M-C-u. In your example, that would bring you to the open parenthesis. To move backwards over individual elements in the list, use backward-sexp, bound to M-C-b; forward-sexp moves the other way, and is bound to M-C-f. From the beginning of an sexp, you can skip to the next with M-C-n; reverse with M-C-p.
None of these commands are actually looking at the physical line you are on, so they'll go back or forward over multiple lines.
Other options include Ace Jump mode, which is a very slick way to quickly navigate to the beginning of any word visible on the screen. That might eliminate your need to use line-specific commands. For quick movement within a line, I usually use M-f and M-b to jump over words. Holding the M key down while tapping on b or f is quick enough that I end up using that by default most of the time.
Edit:
Forgot one other nice command - back-to-indentation, bound to M-m. This will back you up to the first non-whitespace character in a line. You could advice this to behave normally on the first call, and then to back up to the beginning of the line on the second call:
(defadvice back-to-indentation (around back-to-back)
(if (eq last-command this-command)
(beginning-of-line)
ad-do-it))
(ad-activate 'back-to-indentation)
I just wrote these two functions that have the behavior you are looking for.
(defun move-beginning-indent ()
(interactive)
(if (eq last-command this-command)
(beginning-of-line)
(back-to-indentation))
)
(defun move-end-indent ()
(interactive)
(if (eq last-command this-command)
(end-of-line)
(end-of-line)
(search-backward-regexp "\\s)" nil t) ; searches backwards for a
(forward-char 1)) ; closed delimiter such as ) or ]
)
(global-set-key [f7] 'move-beginning-indent)
(global-set-key [f8] 'move-end-indent)
Just try them out, they should behave exactly the way you'd want them to.
I use this:
(defun beginning-of-line-or-text (arg)
"Move to BOL, or if already there, to the first non-whitespace character."
(interactive "p")
(if (bolp)
(beginning-of-line-text arg)
(move-beginning-of-line arg)))
(put 'beginning-of-line-or-text 'CUA 'move)
;; <home> is still bound to move-beginning-of-line
(global-set-key (kbd "C-a") 'beginning-of-line-or-text)
(defun end-of-code-or-line ()
"Move to EOL. If already there, to EOL sans comments.
That is, the end of the code, ignoring any trailing comment
or whitespace. Note this does not handle 2 character
comment starters like // or /*. Such will not be skipped."
(interactive)
(if (not (eolp))
(end-of-line)
(skip-chars-backward " \t")
(let ((pt (point))
(lbp (line-beginning-position))
(comment-start-re (concat (if comment-start
(regexp-quote
(replace-regexp-in-string
"[[:space:]]*" "" comment-start))
"[^[:space:]][[:space:]]*$")
"\\|\\s<"))
(comment-stop-re "\\s>")
(lim))
(when (re-search-backward comment-start-re lbp t)
(setq lim (point))
(if (re-search-forward comment-stop-re (1- pt) t)
(goto-char pt)
(goto-char lim) ; test here ->
(while (looking-back comment-start-re (1- (point)))
(backward-char))
(skip-chars-backward " \t"))))))
(put 'end-of-code-or-line 'CUA 'move)
;; <end> is still bound to end-of-visual-line
(global-set-key (kbd "C-e") 'end-of-code-or-line)

emacs 23.2.1: character-by-character copying from the previous line seems broken

After upgrading to emacs 23.2.1 with Fedora 15 one of my emacs functions is broken and I just can't seem to find the problem. Something has to have changed, but I sure can't find it in the elisp documentation.
I'd really appreciate any suggestions!
What it should do: Pressing Ctl-hyphen should copy the character above the cursor and move the point ahead by 1.
What it does: Keeps copying the character above without advancing the point. HOWEVER, calling it with "M-x insert-char-from-previous-line" or pressing RightArrow between Ctl-hypens works as it should. Adding (forward-char 1) to the end of the script doesn't help.
(defun insert-char-from-previous-line ()
"Copy previous line character by character"
(interactive)
(previous-line 1)
(let ((char-above (following-char) ))
(next-line 1)
(if (not (char-equal char-above ?\n ))
(insert char-above))))
(global-set-key [?\C--] 'insert-char-from-previous-line) ;copy char above
I think your getting caught in a goal column issue. You probably should not use next-line/previous-line for this, rather try this:
(defun insert-char-from-previous-line ()
"Copy previous line character by character"
(interactive)
(let ((curpoint (point))
(curcolumn (current-column)))
(forward-line -1)
(forward-char curcolumn)
(let ((char-above (following-char) ))
(goto-char curpoint)
(if (not (char-equal char-above ?\n ))
(insert char-above)))))
From the docs on pervious-line:
If you are thinking of using this in a Lisp program, consider using
`forward-line' with a negative argument instead. It is usually easier
to use and more reliable (no dependence on goal column, etc.).
(describe-function 'previous-line)
don't move the point for something like this:
(defun i-char (arg)
(interactive "*p")
(let ((start (+ (point-at-bol 0)
(current-column)))))
(insert (buffer-substring-no-properties start (+ start arg))))

Get Emacs to join lines when killing lines

When I hit C-k, Emacs kills to end-of-line. When I hit C-k again, it "kills the newline" and brings the next line up. However, the next line's indentation remains intact, and you can end up with a line that has lots of spaces in the middle.
So, from this:
previous line material
next line material
to this:
previous line material next line material
I understand I can use M-^ to join lines properly, but that requires the cursor to be on the next line. How do I modify C-k so that when it kills the newline, also kills the next line's indentation?
For C-k I don't know, but you could use the just-one-space function to transform any number of space into juste one space (it's bound on M-space)
If you give M-^ an argument (for example C-u M-^), it will join the next line to the current line.
Here's a way to plug in the behavior (I think) you want into kill-line: when killing a newline, also kill the indentation that follows. Note that this may result in no space being present after the cursor, which is why I think I'd go with M-1 M-^ or C-k M-SPC instead.
(defadvice kill-line (around kill-indentation
activate compile)
"When killing a line break, also kill any subsequent indentation."
(let ((f-v-l (symbol-function 'forward-visible-line)))
(flet ((forward-visible-line (arg)
(funcall f-v-l arg)
(skip-chars-forward " \t")))
ad-do-it)))
I have this in my .emacs:
(defun pull-line ()
"Pull the next line that contains anything up to the end of this one"
(interactive)
(save-excursion
(end-of-line)
(while (looking-at "[ \n\r\t]")
(delete-char 1))
(if (looking-back "^[[:blank:]]*[[:punct:][:alnum:]].*")
(fixup-whitespace)
(indent-according-to-mode))))
(global-set-key "\C-cp" 'pull-line)
It pulls the next non-blank line up to the this one, and if there is anything on this line it makes calls (fixup-whitespace) which does the right thing about 95% of the time, otherwise it indents it to what emacs thinks is the right level. I think I copied the concept from vim?
I use it all the time, anyway.
I just realized I also have this in my .emacs, it is more exactly what you want, although I forget that I have it since I use pull-line much more often. I think I stole this from emacswiki:
(defun kill-and-join-forward (&optional arg)
"If at end of line, join with following; otherwise kill line.
Deletes whitespace at join."
(interactive "P")
(if (and (eolp) (not (bolp)))
(progn
(delete-indentation t)
(if (looking-at " $")
(delete-char 1)))
(kill-line arg)))
(global-set-key "\C-k" 'kill-and-join-forward)
This will do it:
(defun my-kill-line (&optional arg)
(interactive "P")
(if arg
(kill-line arg)
(when (prog1 (eolp) (kill-line))
(just-one-space 1))))