Subject: bug#64696: 30.0.50; indent-to inherits preceding text properties, including 'invisible

From: Eli Zaretskii <[email-masked]>
Date: Sat, 22 Jul 2023 14:22:47 +0300

> From: Ihor Radchenko <[email-masked]>
> Cc: [email-masked], [email-masked]
> Date: Sat, 22 Jul 2023 07:03:41 +0000
> 
> Eli Zaretskii <[email-masked]> writes:
> 
> >> You can do M-x yant/full-test after evaluating the code below
> >
> > And what are the problems you see with current-column in this example?
> > (Let's leave indent-to alone for now.)
> 
> ⛔ Warning (emacs): Test #1:: Everything visible.
> ⛔ Warning (emacs): Moved point after first ’word’
> ⛔ Warning (emacs): 1:: current-column = 4
> 
> Test #1 is expected - everything is visible, we are indeed at column 4.
> 
> ⛔ Warning (emacs): Test #2:: ’word’ is inside folded heading (hidden using overlays).
> ⛔ Warning (emacs): Moved point after first ’word’
> ⛔ Warning (emacs): 1:: current-column = 4
> 
> Test #2 is unexpected - we are inside invisible region, but
> current-column reports as if everything were visible.

current-column produces incorrect results when the newline before the
current line is invisible.  It always starts from the beginning of the
current physical line, even if that is in invisible text.

We could teach current-column about invisible newlines, see the patch
below.  But I'm not sure this is justified, nor whether it won't break
something.  The patch below also has a disadvantage that it will still
behave as before for a buffer that is not displayed in any window; if
we want that to be fixed as well, the changes will need to be more
extensive.  (Basically, we will need to write a non-display version of
back_to_previous_visible_line_start.)

With the patch below, Test #2 shows "current-column = 6", which is
correct, since the cursor is shown after "* Test", with all the rest
invisible.

If we think this kind of change is a good idea (Stefan?), patches to
make the below work without employing display code (which needs the
window) will be welcome.

diff --git a/src/indent.c b/src/indent.c
index eda85f2..1357fd2 100644
--- a/src/indent.c
+++ b/src/indent.c
@@ -563,11 +563,35 @@ scan_for_column (ptrdiff_t *endpos, EMACS_INT *goalcol,
   ptrdiff_t end = endpos ? *endpos : PT;
   ptrdiff_t scan, scan_byte, next_boundary, prev_pos, prev_bpos;
 
-  scan = find_newline (PT, PT_BYTE, BEGV, BEGV_BYTE, -1, NULL, &scan_byte, 1);
-
   window = Fget_buffer_window (Fcurrent_buffer (), Qnil);
   w = ! NILP (window) ? XWINDOW (window) : NULL;
 
+  int prevbyte = 0;
+  Lisp_Object prop = Qnil;
+  if (PT > BEGV && w)
+    {
+      prevbyte = FETCH_BYTE (PT_BYTE - 1);
+      prop  = Fget_char_property (make_fixnum (PT - 1), Qinvisible, window);
+    }
+  if (w && !(prevbyte == '\n' && TEXT_PROP_MEANS_INVISIBLE (prop) == 0))
+    {
+      struct it it;
+      struct text_pos pt;
+      specpdl_ref count = SPECPDL_INDEX ();
+      void *itdata = bidi_shelve_cache ();
+      SET_TEXT_POS (pt, PT, PT_BYTE);
+      record_unwind_protect_void (unwind_display_working_on_window);
+      display_working_on_window_p = true;
+      start_display (&it, w, pt);
+      reseat_at_previous_visible_line_start (&it);
+      scan = IT_CHARPOS (it);
+      scan_byte = IT_BYTEPOS (it);
+      bidi_unshelve_cache (itdata, 0);
+      unbind_to (count, Qnil);
+    }
+  else
+    scan = find_newline (PT, PT_BYTE, BEGV, BEGV_BYTE, -1, NULL, &scan_byte, 1);
+
   if (current_buffer->long_line_optimizations_p)
     {
       bool lines_truncated = false;