I'm new to elisp, so please forgive me if the following approach is totally clumsy.
In the team I'm currently working with, there is an usual convention of closing python blocks with a pass statement (if they aren't ended by closing keywords like else or except or such). While unusual, this has the advantage that one can always recover the original indentation of the program if it is unintentionally changed (using emacs indent-region).
To get existing code in line with this convention, I wrote a small elisp function:
(defun python-check-indent ()
"Check if automatic indentation changes current indent, insert pass keyword if it does."
(interactive)
(move-beginning-of-line 1)
(skip-chars-forward " ")
(if
(< 0
(let (original)
(setq original (point))
(indent-for-tab-command)
(- (point) original)
)
)
(progn
(insert "pass")
(newline)
(indent-for-tab-command)
)
)
(next-line)
)
(global-set-key (kbd "C-`") 'python-check-indent)
The idea is simply to test whether hitting TAB would change the indentation, and insert a pass statement in that case. To facilitate processing longer blocks of code, it then advances to the next line.
When I run it using M-x python-check-indent, it does what I want (except that it moves around empty lines slightly), also when running it repeatedly to process several lines. However, when I run it repeatedly using the C-` keybinding, it starts messing up the code from the second invocation on.
So here are my questions: What is the difference between invoking a command with M-x ... and using its keybinding? And how could I change the function to be not affected by this difference?
emacs-version: GNU Emacs 23.3.1 (x86_64-apple-darwin, NS apple-appkit-1038.35) of 2011-03-10 on black.porkrind.org
(edit) current workaround: I'm now wrapping it inside a keyboard-macro, so it's "bound" to C-x e, and behaves properly.
The general rule is that it is best to avoid complex interactive
commands in your functions because they could be affected by all sorts
of options.
(defun python-check-indent ()
"Check if automatic indentation changes current indent, insert pass keyword if it does."
(interactive)
(goto-char (line-beginning-position))
(skip-chars-forward " ")
(when (< 0
(let (original)
(setq original (point))
(python-indent-line)
(- (point) original)))
(insert "pass\n")
(python-indent-line))
(forward-line))
However, even this is probably not good because python-indent-line's behavior depends on last-command and python-indent-trigger-commands. I think it would be best if you replaced the first invocation of python-indent-line with the code which computes the target indentation instead of actually indenting, something like (nth python-indent-current-level python-indent-levels).
PS. If you still have problems, I suggest that you use edebug and step through the function.
Related
Here's the bigger picture of what I'm trying to do:
With a keypress, it will insert an opening and closing parenthesis right after/before the innermost closing/opening delimiter (bracket, brace, parenthesis, whatever).
But if it is pressed immediately after, it will undo the last insertions, seek out the next closing delimiter, and put it after that, and so on.
I have a working version of the first part, but am looking for "best practices" for the second part (where it undoes and moves outwards)
As a motivator and concrete example, this is a typical scenario when coding in Python. I work with list comprehensions a lot, and often I construct the list, and then decide I want to sum all elements, etc. So I would first type:
[x*x for x in some_lst if is_prime(x)]
and then I'll want to encapsulate this list with a "sum" command:
sum([x*x for x in some_lst if is_prime(x)])
I don't want to have to keep moving the cursor to both the beginning and the end just to insert the parentheses. I'd rather have the point in the list, press a keystroke, have it figure out the delimiters, and place the mark just before the opening inserted parenthesis so that I can type "sum". My function below seems to work (piggybacking on the "expand-region" package):
(defun add-paren ()
(interactive)
(er/mark-outside-pairs)
(exchange-point-and-mark)
(insert-string ")")
(exchange-point-and-mark)
(insert-string "(")
(left-char 1)
)
What's the best practice for the 2nd step?
(Any suggestions/improvements to the above would also be appreciated. This is my first "real" function in Elisp.)
Thanks.
Update: Thanks everyone for the tips. I'll probably use some of them in my final solution. My original question still stands: Is there a standard pattern of "undoing and redoing at a larger scale", or will each problem have its own custom solution? Suppose I use smartparens as suggested to do it all in one keystroke, but I want it to occur on the 3rd level out. What I want is to press the keystroke 3 times and have it place the parentheses there.
So after the first keystroke, it places the parentheses at the innermost level. Pressing it again should remove the inserted parentheses, and place them in the next level up, and so on...
(BTW, not trying to reinvent the wheel. I suspect some of the packages listed may have exactly what I need - I just want practice coding in Elisp).
Update 2:
I guess there is no best practice for this? Anyway, I solved the problem using both expand-region and smartparens:
(defun add-paren ()
(interactive)
(if (eq last-command 'add-paren)
;; (message "AAAA")
(delete-paren)
)
(setq currpoint (point))
(er/mark-outside-pairs)
(if (eq currpoint (point))
(er/mark-outside-pairs)
)
(sp-wrap-with-pair "(")
(left-char 1)
)
(global-set-key (kbd "<f5>") 'add-paren)
(defun delete-paren ()
(interactive)
(setq currloc (point))
(sp-unwrap-sexp)
(goto-char currloc)
(left-char 1)
)
You're already using expand-region. Why not combine that with one of the many "surround region with..." modes?
I personally like smartparens (available via Marmalade or MELPA), but there are many other similar tools.
Use er/expand-region until you've got an appropriate selection, then
( to wrap in parentheses.
When programming, there are several hundred slightly different edit-tasks of this kind. Therefor created a toolkit for it.
In example given, the form might be described as delimited, more precisely bracketed.
ar-bracketed-atpt would mark it.
It's set here like this:
(global-set-key [(super \])] 'ar-bracketed-atpt)
Then comes in another class of commands which do several things on active region. In this case:
M-x ar-parentize-or-copy-atpt RET
It is bound to C-c )
A tarball for all this stuff is available here:
https://launchpad.net/s-x-emacs-werkstatt/
FWIW, I'd do it as follows:
go before the open bracket.
type sum C-M-SPC (
The C-M-SPC selects the parenthesized (well, "bracketized") expression, and the subsequent ( wraps it in parens (because of electric-pair-mode).
I guess there is no best practice for this? Anyway, I solved the problem using both expand-region and smartparens:
(defun add-paren ()
(interactive)
(if (eq last-command 'add-paren)
;; (message "AAAA")
(delete-paren)
)
(setq currpoint (point))
(er/mark-outside-pairs)
(if (eq currpoint (point))
(er/mark-outside-pairs)
)
(sp-wrap-with-pair "(")
(left-char 1)
)
(global-set-key (kbd "<f5>") 'add-paren)
(defun delete-paren ()
(interactive)
(setq currloc (point))
(sp-unwrap-sexp)
(goto-char currloc)
(left-char 1)
)
I'm writing a derived mode, based on comint-mode. The mode is an interface to a command line program (GRASS gis), and the comint mode completion works for the programs. I'm trying to add on support for completing the arguments to the program, via completion-at-point-functions. A toy example is:
(setq my-commands
'(("ls"
("my-completion-1")
("my-completion-2"))
("mv"
("my-completion-3")
("my-completion-4"))))
(defun my-completion-at-point ()
(interactive)
(let ((pt (point)) ;; collect point
start end)
(save-excursion ;; collect the program name
(comint-bol)
(re-search-forward "\\(\\S +\\)\\s ?"))
(if (and (>= pt (match-beginning 1))
(<= pt (match-end 1)))
() ;; if we're still entering the command, pass completion on to
;; comint-completion-at-point by returning nil
(let ((command (match-string-no-properties 1)))
(when (member* command my-commands :test 'string= :key 'car)
;; If the command is one of my-commands, use the associated completions
(goto-char pt)
(re-search-backward "\\S *")
(setq start (point))
(re-search-forward "\\S *")
(setq end (point))
(list start end (cdr (assoc command my-commands)) :exclusive 'no))))))
(push 'my-completion-at-point completion-at-point-functions)
This almost works. I get normal completion of program names. However, if I have entered ls at the command line, hitting tab inserts my-completion- and doesn't offer the two options. Hitting tab again inserts my-completion- a second time, so that I now have ls my-completion-mycompletion-.
My actual code includes a few lines to check for multi-line commands, but makes no changes to the completion code. With this version of the code, I hitting tab on a line that starts with one of the program names in my-commands I am presented with a list of the possible arguments to complete the command with, but nothing is inserted in the buffer, and the list does not get narrowed by typing the first few letters of an argument.
I've been over the manual, but I can't figure out the correct way to write a completion-at-point function. Any ideas what I'm missing?
I have looked briefly at pcomplete, but the didn't really understand the 'documentation', and didn't make any progress.
The problem seems to be with the way you're finding start and end to return the boundaries of the argument at point. I didn't spend long enough debugging it to be sure of the details, but I think if you call the function interactively you'll see that it returns the same value for start and end, and this means that the completion UI doesn't know to use the argument at point to select from the completion table you've passed it.
Changing the last part of your function to the following seems to be one fix:
(when (member* command my-commands :test 'string= :key 'car)
;; If the command is one of my-commands, use the associated completions
(goto-char pt)
(let ((start
(save-excursion
(skip-syntax-backward "^ ")
(point))))
(list start pt (cdr (assoc command my-commands)) :exclusive 'no)))))))
This gives the expected results when added as an element of completion-at-point-functions.
Here I've used skip-syntax-backward instead of regexp search, which I think is slightly more idiomatic Elisp for this kind of thing. It just says to move point backwards across anything that is not in syntax class "whitespace". The skip-syntax functions return the distance moved rather than the value of point, so we have to add a call to point at the end of the save-excursion.
If you do use regexp searches in a function like this, it's usually a good idea to pass t for the fourth argument, noerror, so that it doesn't pass on errors to the user if it fails to match. This does mean that you have to check for yourself whether the return value is nil, though.
Finally, instead of push to add the completion function you might want to use add-hook as follows:
(add-hook 'completion-at-point-functions 'my-completion-at-point nil t)
This does two useful things: it checks whether your function is already in the hook before adding it, and (by passing t for the fourth argument, local) it only adds the function to the buffer-local value of the completion-at-point hook. This is almost certainly what you want, since you don't want to use these completions in every other Emacs buffer when you press the TAB key.
I wrote an interactive function which inserts the "character above the point" in to the current line. For instance, given a line containing "12345" followed by a line "abcdef" and the point sitting at the letter "c", copy-down would make the second line become "ab3cdef". copy-down again would make the second line become "ab34cdef".
My function fails (using GNU Emacs 23.3.1 under windows 7) the second time I invoke it by inserting the text from the first invocation and not advancing properly. If I put any emacs "manipulations" in-between invocations, it works fine. (For instance if I do a copy-down, "left arrow", "right arrow", copy-down it works fine for both invocations.)
Here's my function:
(defun copy-down ()
"Grab the character in the line above and insert at the current location."
(interactive)
(let ((beg (progn (previous-line 1) (point)))
(end (progn (forward-char) (point))))
(backward-char)
(next-line 1)
(insert-buffer-substring (current-buffer) beg end)))
If it matters, I usually tie my function to a key: (global-set-key [f5] 'copy-down)
PS. I got used to using this capability in the editor I used before switching to emacs many years ago and I miss it in GNU Emacs. :-(
What you have works just fine for me. That said, previous-line has interaction with other settings (specifically goal-column) and generally shouldn't be used when writing elisp. Instead you should use (forward-line -1). But, of course, your code relies on the goal-column... You can test this by running Emacs without your other configurations, ala emacs -q.
Here's a slightly different version of your code that doesn't rely on goal-column:
(defun copy-down ()
"Grab the character in the line above and insert at the current location."
(interactive)
(let* ((col (current-column))
(to-insert (save-excursion
(forward-line -1)
(move-to-column col)
(buffer-substring-no-properties (point) (1+ (point))))))
(insert to-insert)))
If the problem isn't with using previous-line, then I don't imagine my code would make much of a difference.
Another option you have is to try running it in the debugger to see where your code breaks down. Move the point inside the defun for copy-down and type M-x edebug-defun, and the next time you run it you'll be able to step through the code. Docs for edebug can be found here.
You need to use let* instead of let. The former allows you to use earlier values in later forms in the same statement.
BTW, that's an unconventional way to write elisp, you might want to look at some other code samples.
EDIT:
Hey, someone completely rearranged your function! It might work now.
Try
(defun copy-down (arg)
(interactive "p")
(let ((p (+ (current-column) (point-at-bol 0))))
(insert-buffer-substring (current-buffer) p (+ p arg))))
which has the additional functionality of taking a prefix argument to copy n (default to 1) characters down.
When starting Emacs, init.el (or .emacs.el) is evaluated. However, when starting emacsclient, no similar lisp code is evaluated.
How can I get a lisp file to be evaluated every time I open a new emacsclient?
(This would be handy for frame specific customizations.)
I assume the answer is to use some hook, but I can't seem to find the correct hook to use.
I look forward to your answers.
You can add a function to the hook 'server-visit-hook, which is run every time the server is called (every time you call emacsclient).
I use the following code to automatically change the behavior of server buffers. I use it especially with the Firefox extension It's All Text. In that extension, buffers are named according to the domain name, so you can figure out which rule to apply by using string-match to match the name of the file.
(defun server-edit-presets ()
(cond
;; When editing mail, set the goal-column to 72.
((string-match "mail\\.google\\.com\\.[0-9a-z]+\\.txt" (buffer-name))
(longlines-mode-off)
(auto-fill-mode 1)
(set-fill-column 72)
(save-excursion
;; Don't know if this is necessary, but it seems to help.
(set-buffer (buffer-name))
(goto-char (point-min))
;; Replace non-breaking strange space characters
(while (search-forward (char-to-string 160) nil t)
(replace-match " "))))))
(add-hook 'server-visit-hook 'server-edit-presets)
(add-hook 'server-visit-hook '(lambda () (longlines-mode 1)))
If you really want new frame customizations, there's create-frame-hook which takes one arg (the new frame)...
If you mean gnuclient, you can use the command-line option "-eval" to evaluate something (and then just make an alias to always eval your customizations).
#LSW:
Try 'window-setup-hook. This addresses the annoyance since it is called even if emacsclient is not passed a file.
It seems that those hooks are no more, so here's the new version.
(add-hook 'server-after-make-frame-hook 'consult-recent-file)
I frequently find myself typing on a line, when I realize I need(ed) a variable definition (or something similar) on the line above. What I would like is to
press C-return from anywhere on a line and have the cursor move to a newly inserted blank line above, with correct indentation (or at least the same as the original line).
be able to yank any text...
and C-u C-space to get back to the original position
I've managed to do #1, but my emacs-fu isn't strong enough to do the rest.
Here's my humble solution:
(defun my-insert-before-line ()
(interactive)
(save-excursion
(beginning-of-line)
; I've changed the order of (yank) and (indent-according-to-mode)
; in order to handle the case when yanked line comes with its own indent
(yank)(indent-according-to-mode)
; could be as well changed to simple (newline) it's metter of taste
; and of usage
(newline-and-indent)))
Hope it helps.
Here's what you can do if you are not a Zen master emacs dude.
Emacs has a record-macro thing, kmacro-start-macro and kmacro-end-macro.
After recording your macro, do name-last-kbd-macro. then visit .emacs, and do insert-kbd-macro.
You then have an fset statement that defines your macro. It may look funny, and it is not as maintainable as elisp, but if you stuff it into your .emacs, that macro (by that name) will be available to any of your editing sessions. And you can bind it to a key sequence as well.
Probably bad form to answer my own question, but Cheeso's answer motivated me to do some lisp programming for the second time in ten years (my original version was a named keyboard macro, but it stepped all over the kill/mark-rings). Here's what I came up with
(defun insert-and-indent-line-above ()
(interactive)
(push-mark)
(let*
((ipt (progn (back-to-indentation) (point)))
(bol (progn (move-beginning-of-line 1) (point)))
(indent (buffer-substring bol ipt)))
(newline)
(previous-line)
(insert indent)))
(global-set-key [ (control return) ] 'insert-and-indent-line-above)
there are probably many better ways of doing this, but two hours of lisp-hacking can hardly be called wasted time :-)