Subject: Re: 23.0.60; left/right-margin property is not honored on word-wrapped lines

From: "Lennart Borgman (gmail)" <[email-masked]>
Date: Sun, 21 Sep 2008 05:27:31 +0200

Chong Yidong wrote:
> "Lennart Borgman (gmail)" <[email-masked]> writes:
>
>> We are talking about the visual representation. Are you saying that the
>> user will be fooled by the "faked" whitespace?
>>
>> I do not think that will be the case here. Or, not more than the "faked"
>> new lines.
>
> Plenty of computer programs "fake" whitespace at the end of the line,
> but none fake it at the beginning.  Users will be fooled for this
> reason.

I think that depends on the type of program. Applications for writing
text (like Word or OpenOffice) have concepts like indenting of
paragraphs. It is something like that I want to achieve here.

>> I just think that a visual representation where the continuation lines
>> are (visually aka faked) indented as the first line is a more useful
>> visual representation. We have already made that conclusion in fill
>> paragraph.
>>
>> Typical use cases I can see is for example in org-mode or when editing
>> text parts of XHTML files.
>
> This can be left to the discretion of individual major modes, by setting
> wrap-prefix or some other method.  I don't think it's desireable to do
> it in general, and left-margin/right-margin is almost certainly the
> wrong variable for this.

Yes, left-margin/right-margin might be a bad and clumsy idea. Using the
actual indentation seems more easy.

I tried to implement something like I suggested here in elisp instead.
It seems usable (to my surprise, I thought it would be too slow). Note
that the mumamo-* macro below is just a copy from jit-lock (if I
remember correctly).

When indenting a line the continuation lines will get the same visual
indentation.

;; Fix-me: There is a confusion between buffer and window margins
;; here. Also the doc says that left-margin-width and dito right may
;; be nil. However they seem to be 0 by default, but when displaying a
;; buffer in a window then window-margins returns (nil).
(defun wrap-to-fill-set-values ()
  "Use `fill-column' display columns in buffer windows."
  (let ((buf-windows (get-buffer-window-list (current-buffer))))
    (dolist (win buf-windows)
      (if wrap-to-fill-mode
          (let* ((edges (window-edges win))
                 (win-width (- (nth 2 edges) (nth 0 edges)))
                 (extra-width (- win-width fill-column))
                 (left-marg (if wrap-to-fill-left-marg-use
                                wrap-to-fill-left-marg-use
                              (- (/ extra-width 2) 1)))
                 (right-marg (- win-width fill-column left-marg))
                 (win-margs (window-margins win))
                 (old-left (or (car win-margs) 0))
                 (old-right (or (cdr win-margs) 0)))
            (unless (> left-marg 0) (setq left-marg 0))
            (unless (> right-marg 0) (setq right-marg 0))
            (unless (and (= old-left left-marg)
                         (= old-right right-marg))
              (set-window-margins win left-marg right-marg)))
        (set-window-buffer win (current-buffer))))))

(defcustom wrap-to-fill-left-marg nil
  "Left margin handling for `wrap-to-fill-mode'.
Used by `wrap-to-fill-mode'. If nil then center the display
columns. Otherwise it should be a number which will be the left
margin."
  :type '(choice (const :tag "Center" nil)
                 (integer :tag "Left margin"))
  :group 'convenience)
(make-variable-buffer-local 'wrap-to-fill-left-marg)

(defvar wrap-to-fill-left-marg-use 0)
(make-variable-buffer-local 'wrap-to-fill-left-marg-use)

(defcustom wrap-to-fill-left-marg-modes
  '(text-mode
    fundamental-mode)
  "Major modes where `wrap-to-fill-left-margin' may be nil."
  :type '(repeat commandp)
  :group 'convenience)

(defun wrap-to-fill-set-prefix (min max)
  "Set `wrap-prefix' text property from point MIN to MAX."
  (let ((here (point))
        beg-pos
        end-pos
        ind-str
        (inhibit-field-text-motion t)
        )
    (goto-char min)
    (forward-line 0)
    (when (< (point) min) (forward-line))
    (mumamo-with-buffer-prepared-for-jit-lock
     (while (and (<= (point) max)
                 (< (point) (point-max)))
       (setq beg-pos (point))
       (setq end-pos (line-end-position))
       (when (equal (get-text-property beg-pos 'wrap-prefix)
                    (get-text-property beg-pos 'wrap-to-fill-prefix))
         (skip-chars-forward "[:blank:]")
         (setq ind-str (buffer-substring-no-properties beg-pos (point)))
         (put-text-property beg-pos end-pos 'wrap-prefix ind-str)
         (put-text-property beg-pos end-pos 'wrap-to-fill-prefix ind-str))
       (forward-line)))
    (goto-char here)))

(defun wrap-to-fill-after-change (min max old-len)
  "For `after-change-functions'.
See the hook for MIN, MAX and OLD-LEN."
  (let ((here (point))
        (inhibit-field-text-motion t))
    (goto-char min)
    (setq min (line-beginning-position))
    (goto-char max)
    (setq max (line-end-position))
    (wrap-to-fill-set-prefix min max)))

(defun wrap-to-fill-scroll-fun (window start-pos)
  "For `window-scroll-functions'.
See the hook for WINDOW and START-POS."
  (let ((min (or start-pos (window-start window)))
        (max (window-end window t)))
    (wrap-to-fill-set-prefix min max)))

(define-minor-mode wrap-to-fill-mode
  "Use `fill-column' display columns in buffer windows.
By default the display columns are centered, but see the option
`wrap-to-fill-left-marg'.

Note 1: When turning this on `visual-line-mode' is also turned on. This
is not reset when turning off this mode.

Note 2: The text property `wrap-prefix' is set by this mode to
indent continuation lines.  This is not recorded in the undo
list."
  :lighter " WrapFill"
  :group 'convenience
  (if wrap-to-fill-mode
      (progn
        (setq wrap-to-fill-left-marg-use wrap-to-fill-left-marg)
        (unless (or wrap-to-fill-left-marg-use
                    (memq major-mode wrap-to-fill-left-marg-modes))
          (setq wrap-to-fill-left-marg-use
                (default-value 'wrap-to-fill-left-marg-use)))
        (add-hook 'window-configuration-change-hook
'wrap-to-fill-set-values nil t)
        (add-hook 'after-change-functions 'wrap-to-fill-after-change nil t)
        (add-hook 'window-scroll-functions 'wrap-to-fill-scroll-fun nil t)
        (visual-line-mode 1)
        (dolist (window (get-buffer-window-list (current-buffer)))
          (wrap-to-fill-scroll-fun window nil)))
    (remove-hook 'window-configuration-change-hook
'wrap-to-fill-set-values t)
    (remove-hook 'after-change-functions 'wrap-to-fill-after-change t)
    (remove-hook 'window-scroll-functions 'wrap-to-fill-scroll-fun t)
    (let ((here (point))
          (inhibit-field-text-motion t)
          beg-pos
          end-pos)
      (mumamo-with-buffer-prepared-for-jit-lock
       (save-restriction
         (widen)
         (goto-char (point-min))
         (while (< (point) (point-max))
           (setq beg-pos (point))
           (setq end-pos (line-end-position))
           (when (equal (get-text-property beg-pos 'wrap-prefix)
                        (get-text-property beg-pos 'wrap-to-fill-prefix))
             (remove-list-of-text-properties
              beg-pos end-pos
              '(wrap-prefix)))
           (forward-line))
         (remove-list-of-text-properties
          (point-min) (point-max)
          '(wrap-to-fill-prefix)))
       (goto-char here))))
  (wrap-to-fill-set-values))