/* Buffer insertion/deletion and gap motion for GNU Emacs.
-
-Copyright (C) 1985-1986, 1993-1995, 1997-2014 Free Software Foundation, Inc.
+ Copyright (C) 1985-1986, 1993-1995, 1997-2016 Free Software
+ Foundation, Inc.
This file is part of GNU Emacs.
GNU Emacs 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.
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
GNU Emacs is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
#include <intprops.h>
#include "lisp.h"
+#include "composite.h"
#include "intervals.h"
#include "character.h"
#include "buffer.h"
#include "window.h"
-#include "blockinput.h"
#include "region-cache.h"
static void insert_from_string_1 (Lisp_Object, ptrdiff_t, ptrdiff_t, ptrdiff_t,
/* Buffer which combine_after_change_list is about. */
static Lisp_Object combine_after_change_buffer;
-Lisp_Object Qinhibit_modification_hooks;
-
static void signal_before_change (ptrdiff_t, ptrdiff_t, ptrdiff_t *);
/* Also used in marker.c to enable expensive marker checks. */
if (i == 0)
break;
/* If a quit is requested, stop copying now.
- Change BYTEPOS to be where we have actually moved the gap to. */
+ Change BYTEPOS to be where we have actually moved the gap to.
+ Note that this cannot happen when we are called to make the
+ gap larger or smaller, since make_gap_larger and
+ make_gap_smaller prevent QUIT by setting inhibit-quit. */
if (QUITP)
{
bytepos = new_s1;
if (i == 0)
break;
/* If a quit is requested, stop copying now.
- Change BYTEPOS to be where we have actually moved the gap to. */
+ Change BYTEPOS to be where we have actually moved the gap to.
+ Note that this cannot happen when we are called to make the
+ gap larger or smaller, since make_gap_larger and
+ make_gap_smaller prevent QUIT by setting inhibit-quit. */
if (QUITP)
{
bytepos = new_s1;
QUIT;
}
\f
+/* If the selected window's old pointm is adjacent or covered by the
+ region from FROM to TO, unsuspend auto hscroll in that window. */
+
+static void
+adjust_suspend_auto_hscroll (ptrdiff_t from, ptrdiff_t to)
+{
+ if (WINDOWP (selected_window))
+ {
+ struct window *w = XWINDOW (selected_window);
+
+ if (BUFFERP (w->contents)
+ && XBUFFER (w->contents) == current_buffer
+ && XMARKER (w->old_pointm)->charpos >= from
+ && XMARKER (w->old_pointm)->charpos <= to)
+ w->suspend_auto_hscroll = 0;
+ }
+}
+
+
/* Adjust all markers for a deletion
whose range in bytes is FROM_BYTE to TO_BYTE.
The range in charpos is FROM to TO.
struct Lisp_Marker *m;
ptrdiff_t charpos;
+ adjust_suspend_auto_hscroll (from, to);
for (m = BUF_MARKERS (current_buffer); m; m = m->next)
{
charpos = m->charpos;
ptrdiff_t nchars = to - from;
ptrdiff_t nbytes = to_byte - from_byte;
+ adjust_suspend_auto_hscroll (from, to);
for (m = BUF_MARKERS (current_buffer); m; m = m->next)
{
eassert (m->bytepos >= m->charpos
ptrdiff_t diff_chars = new_chars - old_chars;
ptrdiff_t diff_bytes = new_bytes - old_bytes;
+ adjust_suspend_auto_hscroll (from, from + old_chars);
for (m = BUF_MARKERS (current_buffer); m; m = m->next)
{
if (m->bytepos >= prev_to_byte)
enlarge_buffer_text (current_buffer, nbytes_added);
- /* Prevent quitting in move_gap. */
+ /* Prevent quitting in gap_left. We cannot allow a QUIT there,
+ because that would leave the buffer text in an inconsistent
+ state, with 2 gap holes instead of just one. */
tem = Vinhibit_quit;
Vinhibit_quit = Qt;
if (GAP_SIZE - nbytes_removed < GAP_BYTES_MIN)
nbytes_removed = GAP_SIZE - GAP_BYTES_MIN;
- /* Prevent quitting in move_gap. */
+ /* Prevent quitting in gap_right. We cannot allow a QUIT there,
+ because that would leave the buffer text in an inconsistent
+ state, with 2 gap holes instead of just one. */
tem = Vinhibit_quit;
Vinhibit_quit = Qt;
(2) POS is the last of the current buffer.
(3) A character at POS can't be a following byte of multibyte
character. */
- if (length > 0 && ASCII_BYTE_P (string[length - 1])) /* case (1) */
+ if (length > 0 && ASCII_CHAR_P (string[length - 1])) /* case (1) */
return 0;
if (pos_byte == Z_BYTE) /* case (2) */
return 0;
ptrdiff_t nchars, ptrdiff_t nbytes,
bool inherit, bool before_markers)
{
- struct gcpro gcpro1;
ptrdiff_t outgoing_nbytes = nbytes;
INTERVAL intervals;
= count_size_as_multibyte (SDATA (string) + pos_byte,
nbytes);
- GCPRO1 (string);
/* Do this before moving and increasing the gap,
because the before-change hooks might move the gap
or make it smaller. */
move_gap_both (PT, PT_BYTE);
if (GAP_SIZE < outgoing_nbytes)
make_gap (outgoing_nbytes - GAP_SIZE);
- UNGCPRO;
/* Copy the string text into the buffer, perhaps converting
between single-byte and multibyte. */
/* Update various buffer positions for the new text. */
GAP_SIZE -= len_byte;
- ZV += len; Z+= len;
+ ZV += len; Z += len;
ZV_BYTE += len_byte; Z_BYTE += len_byte;
GPT += len; GPT_BYTE += len_byte;
- if (GAP_SIZE > 0) *(GPT_ADDR) = 0; /* Put an anchor. */
+ if (GAP_SIZE > 0) *(GPT_ADDR) = 0; /* Put an anchor. */
if (nchars_del > 0)
adjust_markers_for_replace (from, from_byte, nchars_del, nbytes_del,
if (from < PT)
adjust_point (len - nchars_del, len_byte - nbytes_del);
- /* As byte combining will decrease Z, we must check this again. */
+ /* As byte combining will decrease Z, we must check this again. */
if (Z - GPT < END_UNCHANGED)
END_UNCHANGED = Z - GPT;
ptrdiff_t insbytes = SBYTES (new);
ptrdiff_t from_byte, to_byte;
ptrdiff_t nbytes_del, nchars_del;
- struct gcpro gcpro1;
INTERVAL intervals;
ptrdiff_t outgoing_insbytes = insbytes;
Lisp_Object deletion;
check_markers ();
- GCPRO1 (new);
deletion = Qnil;
if (prepare)
to = from + range_length;
}
- UNGCPRO;
-
/* Make args be valid. */
if (from < BEGV)
from = BEGV;
outgoing_insbytes
= count_size_as_multibyte (SDATA (new), insbytes);
- GCPRO1 (new);
-
/* Make sure the gap is somewhere in or next to what we are deleting. */
if (from > GPT)
gap_right (from, from_byte);
MODIFF++;
CHARS_MODIFF = MODIFF;
- UNGCPRO;
signal_after_change (from, nchars_del, GPT - from);
update_compositions (from, GPT, CHECK_BORDER);
{
ptrdiff_t from_byte, to_byte;
Lisp_Object deletion;
- struct gcpro gcpro1;
/* Make args be valid */
if (from < BEGV)
to_byte = CHAR_TO_BYTE (to);
deletion = del_range_2 (from, from_byte, to, to_byte, ret_string);
- GCPRO1 (deletion);
signal_after_change (from, to - from, 0);
update_compositions (from, from, CHECK_HEAD);
- UNGCPRO;
return deletion;
}
{
ptrdiff_t from, to;
- /* Make args be valid */
+ /* Make args be valid. */
if (from_byte < BEGV_BYTE)
from_byte = BEGV_BYTE;
if (to_byte > ZV_BYTE)
/* Delete a range of text, specified both as character positions
and byte positions. FROM and TO are character positions,
while FROM_BYTE and TO_BYTE are byte positions.
- If RET_STRING, the deleted area is returned as a string. */
+ If RET_STRING, the deleted area is returned as a string. */
Lisp_Object
del_range_2 (ptrdiff_t from, ptrdiff_t from_byte,
bset_point_before_scroll (current_buffer, Qnil);
}
-Lisp_Object Qregion_extract_function;
+/* Signal that we are about to make a change that may result in new
+ undo information.
+ */
+static void
+run_undoable_change (void)
+{
+ if (EQ (BVAR (current_buffer, undo_list), Qt))
+ return;
+
+ call0 (Qundo_auto__undoable_change);
+}
/* Check that it is okay to modify the buffer between START and END,
which are char positions.
any modification properties the text may have.
If PRESERVE_PTR is nonzero, we relocate *PRESERVE_PTR
- by holding its value temporarily in a marker. */
+ by holding its value temporarily in a marker.
+
+ This function runs Lisp, which means it can GC, which means it can
+ compact buffers, including the current buffer being worked on here.
+ So don't you dare calling this function while manipulating the gap,
+ or during some other similar "critical section". */
void
prepare_to_modify_buffer_1 (ptrdiff_t start, ptrdiff_t end,
ptrdiff_t *preserve_ptr)
{
struct buffer *base_buffer;
+ Lisp_Object temp;
+ XSETFASTINT (temp, start);
if (!NILP (BVAR (current_buffer, read_only)))
- Fbarf_if_buffer_read_only ();
+ Fbarf_if_buffer_read_only (temp);
+
+ run_undoable_change();
bset_redisplay (current_buffer);
if (preserve_ptr)
{
Lisp_Object preserve_marker;
- struct gcpro gcpro1;
preserve_marker = Fcopy_marker (make_number (*preserve_ptr), Qnil);
- GCPRO1 (preserve_marker);
verify_interval_modification (current_buffer, start, end);
*preserve_ptr = marker_position (preserve_marker);
unchain_marker (XMARKER (preserve_marker));
- UNGCPRO;
}
else
verify_interval_modification (current_buffer, start, end);
else
base_buffer = current_buffer;
+ if (inhibit_modification_hooks)
+ return;
+
if (!NILP (BVAR (base_buffer, file_truename))
/* Make binding buffer-file-name to nil effective. */
&& !NILP (BVAR (base_buffer, filename))
/* If `select-active-regions' is non-nil, save the region text. */
/* FIXME: Move this to Elisp (via before-change-functions). */
if (!NILP (BVAR (current_buffer, mark_active))
- && !inhibit_modification_hooks
&& XMARKER (BVAR (current_buffer, mark))->buffer
&& NILP (Vsaved_region_selection)
&& (EQ (Vselect_active_regions, Qonly)
= call1 (Fsymbol_value (Qregion_extract_function), Qnil);
signal_before_change (start, end, preserve_ptr);
- Vdeactivate_mark = Qt;
+ Fset (Qdeactivate_mark, Qt);
}
/* Like above, but called when we know that the buffer text
need to consider the caches of their base buffer. */
if (buf->base_buffer)
buf = buf->base_buffer;
+ /* The bidi_paragraph_cache must be invalidated first, because doing
+ so might need to use the newline_cache (via find_newline_no_quit,
+ see below). */
+ if (buf->bidi_paragraph_cache)
+ {
+ if (start != end
+ && start > BUF_BEG (buf))
+ {
+ /* If we are deleting or replacing characters, we could
+ create a paragraph start, because all of the characters
+ from START to the beginning of START's line are
+ whitespace. Therefore, we must extend the region to be
+ invalidated up to the newline before START. */
+ ptrdiff_t line_beg = start;
+ ptrdiff_t start_byte = buf_charpos_to_bytepos (buf, start);
+
+ if (BUF_FETCH_BYTE (buf, start_byte - 1) != '\n')
+ {
+ struct buffer *old = current_buffer;
+
+ set_buffer_internal (buf);
+
+ line_beg = find_newline_no_quit (start, start_byte, -1,
+ &start_byte);
+ set_buffer_internal (old);
+ }
+ start = line_beg - (line_beg > BUF_BEG (buf));
+ }
+ invalidate_region_cache (buf,
+ buf->bidi_paragraph_cache,
+ start - BUF_BEG (buf), BUF_Z (buf) - end);
+ }
if (buf->newline_cache)
invalidate_region_cache (buf,
buf->newline_cache,
invalidate_region_cache (buf,
buf->width_run_cache,
start - BUF_BEG (buf), BUF_Z (buf) - end);
- if (buf->bidi_paragraph_cache)
- invalidate_region_cache (buf,
- buf->bidi_paragraph_cache,
- start - BUF_BEG (buf), BUF_Z (buf) - end);
}
/* These macros work with an argument named `preserve_ptr'
Lisp_Object start, end;
Lisp_Object start_marker, end_marker;
Lisp_Object preserve_marker;
- struct gcpro gcpro1, gcpro2, gcpro3;
ptrdiff_t count = SPECPDL_INDEX ();
struct rvoe_arg rvoe_arg;
- if (inhibit_modification_hooks)
- return;
-
start = make_number (start_int);
end = make_number (end_int);
preserve_marker = Qnil;
start_marker = Qnil;
end_marker = Qnil;
- GCPRO3 (preserve_marker, start_marker, end_marker);
specbind (Qinhibit_modification_hooks, Qt);
/* If buffer is unmodified, run a special hook for that case. The
- check for Vfirst_change_hook is just a minor optimization. */
+ check for Vfirst_change_hook is just a minor optimization. */
if (SAVE_MODIFF >= MODIFF
&& !NILP (Vfirst_change_hook))
{
PRESERVE_VALUE;
PRESERVE_START_END;
- Frun_hooks (1, &Qfirst_change_hook);
+ run_hook (Qfirst_change_hook);
}
/* Now run the before-change-functions if any. */
if (!NILP (Vbefore_change_functions))
{
- Lisp_Object args[3];
rvoe_arg.location = &Vbefore_change_functions;
rvoe_arg.errorp = 1;
record_unwind_protect_ptr (reset_var_on_error, &rvoe_arg);
/* Actually run the hook functions. */
- args[0] = Qbefore_change_functions;
- args[1] = FETCH_START;
- args[2] = FETCH_END;
- Frun_hook_with_args (3, args);
+ CALLN (Frun_hook_with_args, Qbefore_change_functions,
+ FETCH_START, FETCH_END);
/* There was no error: unarm the reset_on_error. */
rvoe_arg.errorp = 0;
if (! NILP (end_marker))
free_marker (end_marker);
RESTORE_VALUE;
- UNGCPRO;
unbind_to (count, Qnil);
}
if (!NILP (Vafter_change_functions))
{
- Lisp_Object args[4];
rvoe_arg.location = &Vafter_change_functions;
rvoe_arg.errorp = 1;
record_unwind_protect_ptr (reset_var_on_error, &rvoe_arg);
/* Actually run the hook functions. */
- args[0] = Qafter_change_functions;
- XSETFASTINT (args[1], charpos);
- XSETFASTINT (args[2], charpos + lenins);
- XSETFASTINT (args[3], lendel);
- Frun_hook_with_args (4, args);
+ CALLN (Frun_hook_with_args, Qafter_change_functions,
+ make_number (charpos), make_number (charpos + lenins),
+ make_number (lendel));
/* There was no error: unarm the reset_on_error. */
rvoe_arg.errorp = 0;
combine_after_change_list = Qnil;
combine_after_change_buffer = Qnil;
+ DEFSYM (Qundo_auto__undoable_change, "undo-auto--undoable-change");
+
DEFVAR_LISP ("combine-after-change-calls", Vcombine_after_change_calls,
doc: /* Used internally by the function `combine-after-change-calls' macro. */);
Vcombine_after_change_calls = Qnil;