From: Barry OReilly <gundaetiapo@gmail.com>
To: Stefan Monnier <monnier@iro.umontreal.ca>
Cc: emacs-devel@gnu.org
Subject: Re: [RFC] Editing Lisp through changing indentation
Date: Thu, 29 Aug 2013 15:50:08 -0400 [thread overview]
Message-ID: <CAFM41H0S4ERNCcnbyPTReyCAMiLPC9Sg5C0qe2puV2DPUup1xg@mail.gmail.com> (raw)
In-Reply-To: <jwvhae91hjg.fsf-monnier+emacs@gnu.org>
[-- Attachment #1.1: Type: text/plain, Size: 3650 bytes --]
> I suggest you start with an ELPA package. If it turns out to be
> popular, we can later include it in Emacs.
Ok, I'll call it adjust-parens.
In ELPA, 'make archive' expects a package ChangeLog, even while
existing packages don't have one.
$ make archive
rm -r archive-tmp
rm: cannot remove `archive-tmp': No such file or directory
make: [archive-tmp] Error 1 (ignored)
mkdir -p archive-tmp
cp -a packages/. archive-tmp/packages
make process-archive
make[1]: Entering directory `/redacted/linux/boreilly/sw/elpa'
# FIXME, we could probably speed this up significantly with
# rules like "%.tar: ../%/ChangeLog" so we only rebuild the packages
# that have indeed changed.
cd archive-tmp/packages; \
emacs --batch -l
/redacted/linux/boreilly/sw/elpa/admin/archive-contents.el \
-f batch-make-archive
Skipping non-package file README
Wrote /home/boreilly/l/sw/elpa/archive-tmp/packages/ack/ack-pkg.el
Error in adaptive-wrap: (file-error "Opening input file" "no such file or
directory"
"/home/boreilly/l/sw/elpa/archive-tmp/packages/adaptive-wrap/ChangeLog")
make[1]: *** [process-archive] Error 255
make[1]: Leaving directory `/redacted/linux/boreilly/sw/elpa'
make: *** [archive] Error 2
I fixed it with this patch (git diff -w):
diff --git a/admin/archive-contents.el b/admin/archive-contents.el
index 2d588e9..640c285 100644
--- a/admin/archive-contents.el
+++ b/admin/archive-contents.el
@@ -206,6 +206,7 @@ Rename DIR/PKG.el to PKG-VERS.el, delete DIR, and
return the descriptor."
(re-search-backward "^;;;.*ends here")
(re-search-backward "^(provide")
(skip-chars-backward " \t\n")
+ (when (file-readable-p cl)
(insert "\n\n;;;; ChangeLog:\n\n")
(let* ((start (point))
(end (copy-marker start t)))
@@ -213,7 +214,7 @@ Rename DIR/PKG.el to PKG-VERS.el, delete DIR, and
return the descriptor."
(goto-char end)
(unless (bolp) (insert "\n"))
(while (progn (forward-line -1) (>= (point) start))
- (insert ";; ")))
+ (insert ";; "))))
(set (make-local-variable 'backup-inhibited) t)
(basic-save-buffer) ;Less chatty than save-buffer.
(kill-buffer)))
Shall I commit this?
After that, list-packages shows the new adjust-parens package in the
local ELPA archive.
Here are some ELPA README redlines:
diff --git a/README b/README
index 097e430..3d01912 100644
--- a/README
+++ b/README
@@ -29,9 +29,9 @@ each package.
*** Add a multi-file package as a directory, packages/NAME.
-*** Commit your changes the usual way ("bzr add", "bzr commit", etc).
+*** Commit your changes the usual way ("git add", "git commit", etc).
-Changes in the Bzr repository do not immediately propagate to the
+Changes in the Git repository do not immediately propagate to the
user-facing archive (what users see when they do `M-x list-packages').
That is done by deploying the archive.
@@ -67,7 +67,7 @@ and adds them to the archive.
** To access a deployed archive
-To access the archive via HTPP, have a symlink (say) /var/www/packages
+To access the archive via HTTP, have a symlink (say) /var/www/packages
pointing to DEST/packages, and set up Emacs with
(setq package-archives '(("new-elpa" . "http://foo.com/packages")))
The ELPA README mentions having a "site" build target, but:
$ make site
make: *** No rule to make target `site'. Stop.
If the site target to Make is indeed gone, I'll remove wording from
the README about it too.
With no objections, I'll commit the above diffs and the attached
adjust-parens.el file to ELPA.
[-- Attachment #1.2: Type: text/html, Size: 4293 bytes --]
[-- Attachment #2: adjust-parens.el --]
[-- Type: application/octet-stream, Size: 10199 bytes --]
;;; adjust-parens.el --- Indent and dedent Lisp code, automatically adjust close parens -*- lexical-binding: t; -*-
;; Copyright (C) 2013 Free Software Foundation, Inc.
;; Author: Barry O'Reilly <gundaetiapo@gmail.com>
;; Version: 1.0
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;;
;; This package provides commands for indenting and dedenting Lisp
;; code such that close parentheses are automatically adjusted to be
;; consistent with the new level of indentation.
;;
;; When reading Lisp, the programmer pays attention to open parens and
;; the close parens on the same line. But when a sexp spans more than
;; one line, she deduces the close paren from indentation alone. Given
;; that's how we read Lisp, this package aims to enable editing Lisp
;; similarly: automatically adjust the close parens programmers ignore
;; when reading. A result of this is an editing experience somewhat
;; like python-mode, which also offers "indent" and "dedent" commands.
;; There are differences because lisp-mode knows more due to existing
;; parens.
;;
;; To use:
;; (require 'adjust-parens)
;;
;; This binds two keys in Lisp Mode:
;; (local-set-key (kbd "TAB") 'lisp-indent-adjust-parens)
;; (local-set-key (kbd "<backtab>") 'lisp-dedent-adjust-parens)
;;
;; lisp-indent-adjust-parens potentially calls indent-for-tab-command
;; (the usual binding for TAB in Lisp Mode). Thus it should not
;; interfere with other TAB features like completion-at-point.
;;
;; Some examples follow. | indicates the position of point.
;;
;; (let ((x 10) (y (some-func 20))))
;; |
;;
;; After one TAB:
;;
;; (let ((x 10) (y (some-func 20)))
;; |)
;;
;; After three more TAB:
;;
;; (let ((x 10) (y (some-func 20
;; |))))
;;
;; After two Shift-TAB to dedent:
;;
;; (let ((x 10) (y (some-func 20))
;; |))
;;
;; When dedenting, the sexp may have sibling sexps on lines below. It
;; makes little sense for those sexps to stay at the same indentation,
;; because they cannot keep the same parent sexp without being moved
;; completely. Thus they are dedented too. An example of this:
;;
;; (defun func ()
;; (save-excursion
;; (other-func-1)
;; |(other-func-2)
;; (other-func-3)))
;;
;; After Shift-TAB:
;;
;; (defun func ()
;; (save-excursion
;; (other-func-1))
;; |(other-func-2)
;; (other-func-3))
;;
;; If you indent again with TAB, the sexps siblings aren't indented:
;;
;; (defun func ()
;; (save-excursion
;; (other-func-1)
;; |(other-func-2))
;; (other-func-3))
;;
;; Thus TAB and Shift-TAB are not exact inverse operations of each
;; other, though they often seem to be.
;;; Code:
;; Future work:
;; - Consider taking a region as input in order to indent a sexp and
;; its siblings in the region. Dedenting would not take a region.
;; - Write tests
(require 'cl)
(defun last-sexp-with-relative-depth (from-pos to-pos rel-depth)
"Parsing sexps from FROM-POS (inclusive) to TO-POS (exclusive),
return the position of the last sexp that had depth REL-DEPTH relative
to FROM-POS. Returns nil if REL-DEPTH is not reached.
Examples:
Region: a (b c (d)) e (f g (h i)) j
Evaluate: (last-sexp-with-relative-depth pos-a (1+ pos-j) 0)
Returns: position of j
Evaluate: (last-sexp-with-relative-depth pos-a (1+ pos-j) -1)
Returns: position of (h i)
This function assumes FROM-POS is not in a string or comment."
(save-excursion
(goto-char from-pos)
(let (the-last-pos
(parse-state '(0 nil nil nil nil nil nil nil nil)))
(while (< (point) to-pos)
(setq parse-state
(parse-partial-sexp (point)
to-pos
nil
t ; Stop before sexp
parse-state))
(and (not (eq (point) to-pos))
(eq (car parse-state) rel-depth)
(setq the-last-pos (point)))
;; The previous parse may not advance. To advance and maintain
;; correctness of depth, we parse over the next char.
(setq parse-state
(parse-partial-sexp (point)
(1+ (point))
nil
nil
parse-state)))
the-last-pos)))
(defun adjust-close-paren-for-indent ()
"Adjust a close parentheses of a sexp so as
lisp-indent-adjust-parens can indent that many levels.
If a close paren was moved, returns a two element list of positions:
where the close paren was moved from and the position following where
it moved to.
If there's no close parens to move, either return nil or allow
scan-error to propogate up."
(save-excursion
(let ((deleted-paren-pos
(save-excursion
(beginning-of-line)
(backward-sexp)
;; Account for edge case when point has no sexp before it
(if (bobp)
nil
;; If the sexp at point is a list,
;; delete its closing paren
(when (eq (scan-lists (point) 1 0)
(scan-sexps (point) 1))
(forward-sexp)
(delete-char -1)
(point))))))
(when deleted-paren-pos
(let ((sexp-to-close
(last-sexp-with-relative-depth (point)
(progn (end-of-line)
(point))
0)))
(when sexp-to-close
(goto-char sexp-to-close)
(forward-sexp))
;; Note: when no sexp-to-close found, line is empty. So put
;; close paren after point.
(insert ")")
(list deleted-paren-pos (point)))))))
(defun adjust-close-paren-for-dedent ()
"Adjust a close parentheses of a sexp so as
lisp-dedent-adjust-parens can dedent that many levels.
If a close paren was moved, returns a two element list of positions:
where the close paren was moved from and the position following where
it moved to.
If there's no close parens to move, either return nil or allow
scan-error to propogate up."
(save-excursion
(let ((deleted-paren-pos
(save-excursion
(when (< (point)
(progn (up-list)
(point)))
(delete-char -1)
(point)))))
(when deleted-paren-pos
(let ((sexp-to-close
;; Needs to work when dedenting in an empty list, in
;; which case backward-sexp will signal scan-error and
;; sexp-to-close will be nil.
(condition-case nil
(progn (backward-sexp)
(point))
(scan-error nil))))
;; Move point to where to insert close paren
(if sexp-to-close
(forward-sexp)
(backward-up-list)
(forward-char 1))
(insert ")")
;; The insertion makes deleted-paren-pos off by 1
(list (1+ deleted-paren-pos)
(point)))))))
(defun adjust-parens-p ()
"Whether to adjust parens."
(save-excursion
(let ((orig-pos (point)))
(back-to-indentation)
(and (not (use-region-p))
(<= orig-pos (point))))))
(defun adjust-parens-and-indent (adjust-function prefix-arg)
"Adjust close parens and indent the region over which the parens
moved."
(let ((region-of-change (list (point) (point))))
(cl-loop for i from 1 to (or prefix-arg 1)
with finished = nil
while (not finished)
do
(condition-case err
(let ((close-paren-movement
(funcall adjust-function)))
(if close-paren-movement
(setq region-of-change
(list (min (car region-of-change)
(car close-paren-movement)
(cadr close-paren-movement))
(max (cadr region-of-change)
(car close-paren-movement)
(cadr close-paren-movement))))
(setq finished t)))
(scan-error (setq finished err))))
(apply 'indent-region region-of-change))
(back-to-indentation))
(defun lisp-indent-adjust-parens (&optional prefix-arg)
"Indent Lisp code to the next level while adjusting sexp balanced
expressions to be consistent.
This command can be bound to TAB instead of indent-for-tab-command. It
potentially calls the latter."
(interactive "P")
(if (adjust-parens-p)
(adjust-parens-and-indent 'adjust-close-paren-for-indent
prefix-arg)
(indent-for-tab-command prefix-arg)))
(defun lisp-dedent-adjust-parens (&optional prefix-arg)
"Dedent Lisp code to the previous level while adjusting sexp
balanced expressions to be consistent.
Binding to <backtab> (ie Shift-Tab) is a sensible choice."
(interactive "P")
(when (adjust-parens-p)
(adjust-parens-and-indent 'adjust-close-paren-for-dedent
prefix-arg)))
(add-hook 'emacs-lisp-mode-hook
(lambda ()
(local-set-key (kbd "TAB") 'lisp-indent-adjust-parens)
(local-set-key (kbd "<backtab>") 'lisp-dedent-adjust-parens)))
(provide 'adjust-parens)
;;; adjust-parens.el ends here
next prev parent reply other threads:[~2013-08-29 19:50 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
2013-08-28 21:19 [RFC] Editing Lisp through changing indentation Barry OReilly
2013-08-29 0:14 ` Stefan Monnier
2013-08-29 19:50 ` Barry OReilly [this message]
2013-08-29 20:04 ` Stefan Monnier
2013-08-29 20:40 ` Barry OReilly
2013-08-29 22:14 ` Stefan Monnier
2013-08-29 22:30 ` Andreas Schwab
2013-08-29 22:39 ` Stefan Monnier
2013-08-29 22:49 ` Barry OReilly
2013-08-30 2:23 ` Stefan Monnier
2013-08-30 2:48 ` Barry OReilly
2013-08-30 3:24 ` Stefan Monnier
-- strict thread matches above, loose matches on Subject: below --
2013-07-19 3:23 Barry OReilly
2013-07-19 9:23 ` Thien-Thi Nguyen
2013-07-19 15:58 ` Drew Adams
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=CAFM41H0S4ERNCcnbyPTReyCAMiLPC9Sg5C0qe2puV2DPUup1xg@mail.gmail.com \
--to=gundaetiapo@gmail.com \
--cc=emacs-devel@gnu.org \
--cc=monnier@iro.umontreal.ca \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this external index
https://git.savannah.gnu.org/cgit/emacs.git
https://git.savannah.gnu.org/cgit/emacs/org-mode.git
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.