7

I’m using Emacs with AUCTeX in ConTeXt-mode to compose my ConTeXt documents. However, indentation is a little bit inconvenient for me.

Consider the following snippet:

\setuphead
  [section]
  [before={\blank[big]},
   after={\blank[medium]}]

\setuphead
  [subsection]
  [
    before={\blank[big]},
    after={\blank[medium]},
  ]

If I execute indent-region onto this, it should not change (it is indented correctly, i.e. it follows the indentation scheme of the ConTeXt source and the indentation scheme by our local ConTeXt expert Aditya). However, Emacs mangles this code to

\setuphead
  [section]
  [before={\blank[big]},
    after={\blank[medium]}]

  \setuphead
    [subsection]
    [
      before={\blank[big]},
      after={\blank[medium]},
    ]

Especially the second \setuphead should not be indented further at this point.

Can anyone provide some Lisp magic to put in my .emacs to make indentation less painful?

  • 1
    I'll have a look at this (no guarantee I'll fin a solution, though). – giordano Jan 20 '17 at 13:11
  • @giordano Thank you very much for your effort! I would also accept an answer explaining why it is not possible. – Henri Menke Jan 20 '17 at 13:13
  • The easy answer is that currently there are no active AUCTeX developers that use ConTeXt ;-) I just noticed that if [section] is in the same line as \setuphead, then indentation works correctly. I.e., \setuphead[section][ and then you break the line here. I followed the example code at: wiki.contextgarden.net/Command/setuphead. Maybe this is intended to work this way? – giordano Jan 20 '17 at 13:32
  • @giordano I’m following the ConTeXt source here: github.com/contextgarden/context-mirror/search?q=setuphead – Henri Menke Jan 20 '17 at 13:40
  • 5
    @LeonMeier I am not an Emacs user, the question is perfectly clear to me and, it seems, reproducable for other users. – Johannes_B May 20 '17 at 13:46
7
+50

Note: this question led to the inclusion of the following fix in AUCTeX. It'll be available in the ELPA version 11.90.3 and the stable version 11.91.

Warning: this answer does almost what you asked.

Add the following function definition to your init file:

(with-eval-after-load "context"
  (defun ConTeXt-find-indent (&optional virtual)
    "Find the proper indentation of text after point.
VIRTUAL if non-nil indicates that we're only trying to find the
indentation in order to determine the indentation of something
else.  There might be text before point."
    (save-excursion
      (skip-chars-forward " \t")
      (or
       ;; Trust the current indentation, if such info is applicable.
       (and virtual (>= (current-indentation) (current-column))
        (current-indentation))
       ;; Put leading close-paren where the matching open brace would be.
       (condition-case nil
       (and (eq (char-syntax (char-after)) ?\))
        (save-excursion
          (skip-syntax-forward " )")
          (backward-sexp 1)
          (ConTeXt-find-indent 'virtual)))
     (error nil))
       ;; Default (maybe an argument)
       (let ((pos (point))
         (char (char-after))
         (indent 0)
         up-list-pos)
     ;; Look for macros to be outdented
     (cond ((looking-at (concat (regexp-quote TeX-esc)
                    (ConTeXt-environment-stop-name)))
        (setq indent (- indent ConTeXt-indent-basic)))
           ((looking-at ConTeXt-indent-item-re)
        (setq indent (- indent ConTeXt-indent-item))))
     ;; Find the previous point which determines our current indentation.
     (condition-case err
         (progn
           (backward-sexp 1)
           (while (or (> (current-column) (current-indentation))
              ;; Continue going back if we are
              ;; at a hanging optional group.
              (looking-at (regexp-quote ConTeXt-optop)))
         (backward-sexp 1)))
       (scan-error
        (setq up-list-pos (nth 2 err))))
     (cond
      ((= (point-min) pos) 0)  ; We're really just indenting the first line.
      ((integerp up-list-pos)
       ;; Have to indent relative to the open-paren.
       (goto-char up-list-pos)
       (if (and (not ConTeXt-indent-allhanging)
            (> pos (progn (down-list 1)
                  (forward-comment (point-max))
                  (point))))
           ;; Align with the first element after the open-paren.
           (current-column)
         ;; We're the first element after a hanging brace.
         (goto-char up-list-pos)
         (+ indent ConTeXt-indent-basic (ConTeXt-find-indent 'virtual))))
      ;; We're now at the "beginning" of a line.
      ((not (and (not virtual) (eq (char-after) ?\\)))
       ;; Nothing particular here: just keep the same indentation.
       (+ indent (current-column)))
      ;; We're now looking at an item.
      ((looking-at ConTeXt-indent-item-re)
       ;; Indenting relative to an item, have to re-add the outdenting.
       (+ indent (current-column) ConTeXt-indent-item))
      ;; We're looking at an environment starter.
      ((and (looking-at (concat (regexp-quote TeX-esc)
                    (ConTeXt-environment-start-name)))
        (not (looking-at (concat (regexp-quote TeX-esc)
                     (ConTeXt-environment-start-name)
                     ConTeXt-text)))) ; other environments?
       (+ indent (current-column) ConTeXt-indent-basic))
      (t
       (let ((col (current-column)))
         (if (not (and char (eq (char-syntax char) ?\()))
         ;; If the first char was not an open-paren, there's
         ;; a risk that this is really not an argument to the
         ;; macro at all.
         (+ indent col)
           (forward-sexp 1)
           (if (< (line-end-position)
              (save-excursion (forward-comment (point-max))
                      (point)))
           ;; we're indenting the first argument.
           (min (current-column) (+ ConTeXt-indent-arg col))
         (skip-syntax-forward " ")
         (current-column)))))))))))

This is a fixed version of the indenting function provided by AUCTeX. There is only a minor change (the (looking-at (regexp-quote ConTeXt-optop)) line) to adjust indentation after [...].

Now your original code is indented as

\setuphead
  [section]
  [before={\blank[big]},
    after={\blank[medium]}]

\setuphead
  [subsection]
  [
    before={\blank[big]},
    after={\blank[medium]},
  ]

The only thing that I couldn't fix was indentation of after={\blank[medium]}] line, but for multiline settings inside [...] I'd suggest you to do like you did in the second case.

Please, tell me if you find this solution useful in the long run, so that I can install this change upstream.

  • I appended it to ConTeXt-mode-hook using (add-hook 'ConTeXt-mode-hook (lambda () (...))) but nothing happens. If I just put the snippet in my .emacs as is I get Symbol's function definition is void: with-eval-after-load. – Henri Menke Jan 30 '17 at 8:42
  • Which version of emacs are you using? – giordano Jan 31 '17 at 0:40
  • GNU Emacs 24.3.1, but I saw that with-eval-after-load was only introduced in 24.4. I guess it’s time to update :) – Henri Menke Jan 31 '17 at 10:49
  • 3
    @giordano I've been using this code for the last few months and so far your patch works fine for me. I think it can go into the main AucTeX. I also marked the answer as accepted. – Henri Menke May 20 '17 at 0:52
  • 3
    @HenriMenke Great! Thanks for testing, I pushed the fix to AUCTeX, so starting from the ELPA version 11.90.3 you'll have it by default. Keep suggesting improvements ;-) – giordano May 21 '17 at 21:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.