all messages for Emacs-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
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

  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.