edit-indirect icon indicating copy to clipboard operation
edit-indirect copied to clipboard

Preserve indentation based on the beginning of the buffer

Open Fuco1 opened this issue 6 years ago • 7 comments

Let's say I'm editing a "json" region inside a yaml file

/SearchMp:
  post:
    description: Search resource meters at given address
    body:
      application/json:
        type: !include ../lib/search/schema.json
        example: |
          {
              "postalcode": "1011AB",
              "number": 105,
              "status": {
                  "attributes": {
                      "customerType": {
                          "value": "individual"
                      }
                  }
              }
          }

I would like to select the example, edit it and then have it aligned like in the example above. Instead, the body is aligned like in the indirect buffer, which is to say to the very left margin.

org-mode can do this properly so I think it would be possible to steal some code from there, but in either case shouldn't be very difficult. I might try to hack a PR together, would this be accepted?

Fuco1 avatar Oct 24 '17 09:10 Fuco1

Well, I've fixed it with this code

(defun my-after-indirect-edit-realign (beg end)
  (save-excursion
    (goto-char beg)
    (let ((cc (current-column))
          (end-marker (set-marker (make-marker) end)))
      (while (< (progn
                  (forward-line)
                  (point)) end-marker)
        (line-beginning-position)
        (insert (make-string cc 32)))
      (when indent-tabs-mode
        (tabify beg end-marker))
      (set-marker end-marker nil))))

(add-hook 'edit-indirect-after-commit-functions 'my-after-indirect-edit-realign)

Might get bundled along for people to enable or not.

Fuco1 avatar Oct 24 '17 09:10 Fuco1

I might try to hack a PR together, would this be accepted?

Sure.

Fanael avatar Oct 24 '17 15:10 Fanael

I am using this for similar effect:

(require 's)
(require 'dash)

(defvar edit-indirect--left-margin 0)

(defun vbe:compute-left-margin (code)
  "Compute left margin of a string of code."
  (-min
   (-map #'(lambda (line) (length (car (s-match "^\\s-*" line))))
         (-remove 's-blank? (s-lines code)))))

(defun vbe:after-indirect-edit-remove-left-margin ()
  "Remove left-margin and save it into a local variable."
  (let ((lm (vbe:compute-left-margin (buffer-substring (point-min) (point-max)))))
    (indent-rigidly (point-min) (point-max) (* -1 lm))
    (setq-local edit-indirect--left-margin lm)))

(defun vbe:after-indirect-edit-restore-left-margin ()
  "Restore left-margin before commiting."
  (indent-rigidly (point-min) (point-max) edit-indirect--left-margin))

(add-hook 'edit-indirect-after-creation-hook #'vbe:after-indirect-edit-remove-left-margin)
(add-hook 'edit-indirect-before-commit-hook #'vbe:after-indirect-edit-restore-left-margin)

vincentbernat avatar May 10 '18 04:05 vincentbernat

@vincentbernat thanks, this works very well! May I suggest adding

(put 'edit-indirect--left-margin 'permanent-local t) ;; don't lose this setting when another major mode is started

to keep the value even if another major-mode is started.

ecraven avatar Mar 01 '22 15:03 ecraven

I was looking for something similar to this and was working on my own when I hit a wall:

It's easy to trim the indent in the *-after-creation-hook and restore it in *-before-commit-hook, but that doesn't play well with edit-indirect-save: the indent will be restored every time you save, but it is never trimmed after creation, leading to multiply stacked indents.

There doesn't seem to be a good way to re-trim after saving; it seems an edit-indirect-after-commit-hook is called for. Would you consider accepting a PR for that?

amake avatar Oct 19 '22 14:10 amake

I should note that my trimming needs are a more complex version of @Fuco1's described above: I have a JavaScript application where I am often writing SQL in a string:

function doQuery() {
  ...
  const query = `
    SELECT
      foo,
      bar,
    FROM table 
    WHERE condition1
    AND condition2
    ORDER BY something DESC
  `;
  ...
}

I use thing-at-point-bounds-of-string-at-point to grab the whole string (from ` to `) and indirectly edit that; then I trim leading and trailing blank lines, and only then do I remove the longest common indent prefix. I retain the actual strings to restore, rather than a length.

I think this algorithm should be generally useful, but maybe there are use cases it doesn't handle well.

Edit: Here is my implementation: https://github.com/amake/.emacs.d/blob/ddb84ffa5cd6d4cda378e6e59a5e4cc273b6446d/lisp/edit-string.el

And configuration: https://github.com/amake/.emacs.d/blob/ddb84ffa5cd6d4cda378e6e59a5e4cc273b6446d/init.el#L1333-L1346

amake avatar Oct 19 '22 15:10 amake

@vincentbernat thanks, this works very well! May I suggest adding

(put 'edit-indirect--left-margin 'permanent-local t) ;; don't lose this setting when another major mode is started

to keep the value even if another major-mode is started.

I was looking for something similar to this and was working on my own when I hit a wall:

It's easy to trim the indent in the *-after-creation-hook and restore it in *-before-commit-hook, but that doesn't play well with edit-indirect-save: the indent will be restored every time you save, but it is never trimmed after creation, leading to multiply stacked indents.

There doesn't seem to be a good way to re-trim after saving; it seems an edit-indirect-after-commit-hook is called for. Would you consider accepting a PR for that?

Here's my config based on @vincentbernat one with points above considered:

;; <-------------------------
;; https://github.com/Fanael/edit-indirect/issues/6#issuecomment-387945773

(require 's)
(require 'dash)

(defvar edit-indirect--left-margin 0)

(defun vbe/compute-left-margin (code)
  "Compute left margin of a string of code."
  (-min
   (-map #'(lambda (line) (length (car (s-match "^\\s-*" line))))
         (-remove 's-blank? (s-lines code)))))

(defun vbe/edit-indirect/remove-left-margin ()
  "Remove left-margin and save it into a local variable."
  (let ((lm (vbe/compute-left-margin (buffer-substring (point-min) (point-max)))))
    (indent-rigidly (point-min) (point-max) (* -1 lm))
    (setq-local edit-indirect--left-margin lm)
    ;; https://github.com/Fanael/edit-indirect/issues/6#issuecomment-1055542145
    ;; buffer-local variable whose value should not be reset when changing major modes
    (put 'edit-indirect--left-margin 'permanent-local t)))

(defun vbe/edit-indirect/restore-left-margin ()
  "Restore left-margin before commiting."
  (indent-rigidly (point-min) (point-max) edit-indirect--left-margin))

(add-hook 'edit-indirect-after-creation-hook #'vbe/edit-indirect/remove-left-margin)
(add-hook 'edit-indirect-before-commit-hook #'vbe/edit-indirect/restore-left-margin)

(require 'edit-indirect)
;; https://github.com/Fanael/edit-indirect/issues/6#issuecomment-1284144173
(define-key edit-indirect-mode-map [remap save-buffer] #'edit-indirect-commit)
;; >-------------------------

uqix avatar Dec 29 '23 12:12 uqix