Function: subst-char-in-region
subst-char-in-region is a function defined in editfns.c.
Signature
(subst-char-in-region START END FROMCHAR TOCHAR &optional NOUNDO)
Documentation
From START to END, replace FROMCHAR with TOCHAR each time it occurs.
If optional arg NOUNDO is non-nil, don't record this change for undo and don't mark the buffer as really changed. Both characters must have the same length of multi-byte form.
Other relevant functions are documented in the buffer group.
Probably introduced at or before Emacs version 17.
Shortdoc
;; buffer
(subst-char-in-region (point-min) (point-max) ?+ ?-)
Source Code
// Defined in /usr/src/emacs/src/editfns.c
// Skipping highlighting due to helpful-max-highlight.
{
register ptrdiff_t pos, pos_byte, stop, i, len, end_byte;
/* Keep track of the first change in the buffer:
if 0 we haven't found it yet.
if < 0 we've found it and we've run the before-change-function.
if > 0 we've actually performed it and the value is its position. */
ptrdiff_t changed = 0;
unsigned char fromstr[MAX_MULTIBYTE_LENGTH], tostr[MAX_MULTIBYTE_LENGTH];
unsigned char *p;
specpdl_ref count = SPECPDL_INDEX ();
#define COMBINING_NO 0
#define COMBINING_BEFORE 1
#define COMBINING_AFTER 2
#define COMBINING_BOTH (COMBINING_BEFORE | COMBINING_AFTER)
int maybe_byte_combining = COMBINING_NO;
ptrdiff_t last_changed = 0;
bool multibyte_p
= !NILP (BVAR (current_buffer, enable_multibyte_characters));
int fromc, toc;
restart:
validate_region (&start, &end);
CHECK_CHARACTER (fromchar);
CHECK_CHARACTER (tochar);
fromc = XFIXNAT (fromchar);
toc = XFIXNAT (tochar);
if (multibyte_p)
{
len = CHAR_STRING (fromc, fromstr);
if (CHAR_STRING (toc, tostr) != len)
error ("Characters in `subst-char-in-region' have different byte-lengths");
if (!ASCII_CHAR_P (*tostr))
{
/* If *TOSTR is in the range 0x80..0x9F and TOCHAR is not a
complete multibyte character, it may be combined with the
after bytes. If it is in the range 0xA0..0xFF, it may be
combined with the before and after bytes. */
if (!CHAR_HEAD_P (*tostr))
maybe_byte_combining = COMBINING_BOTH;
else if (BYTES_BY_CHAR_HEAD (*tostr) > len)
maybe_byte_combining = COMBINING_AFTER;
}
}
else
{
len = 1;
fromstr[0] = fromc;
tostr[0] = toc;
}
pos = XFIXNUM (start);
pos_byte = CHAR_TO_BYTE (pos);
stop = CHAR_TO_BYTE (XFIXNUM (end));
end_byte = stop;
/* If we don't want undo, turn off putting stuff on the list.
That's faster than getting rid of things,
and it prevents even the entry for a first change.
Also inhibit locking the file. */
if (!changed && !NILP (noundo))
{
record_unwind_protect (subst_char_in_region_unwind,
BVAR (current_buffer, undo_list));
bset_undo_list (current_buffer, Qt);
/* Don't do file-locking. */
record_unwind_protect (subst_char_in_region_unwind_1,
BVAR (current_buffer, filename));
bset_filename (current_buffer, Qnil);
}
if (pos_byte < GPT_BYTE)
stop = min (stop, GPT_BYTE);
while (1)
{
ptrdiff_t pos_byte_next = pos_byte;
if (pos_byte >= stop)
{
if (pos_byte >= end_byte) break;
stop = end_byte;
}
p = BYTE_POS_ADDR (pos_byte);
if (multibyte_p)
pos_byte_next += next_char_len (pos_byte_next);
else
++pos_byte_next;
if (pos_byte_next - pos_byte == len
&& p[0] == fromstr[0]
&& (len == 1
|| (p[1] == fromstr[1]
&& (len == 2 || (p[2] == fromstr[2]
&& (len == 3 || p[3] == fromstr[3]))))))
{
if (changed < 0)
/* We've already seen this and run the before-change-function;
this time we only need to record the actual position. */
changed = pos;
else if (!changed)
{
changed = -1;
modify_text (pos, XFIXNUM (end));
if (! NILP (noundo))
{
modiff_count m = MODIFF;
if (SAVE_MODIFF == m - 1)
SAVE_MODIFF = m;
if (BUF_AUTOSAVE_MODIFF (current_buffer) == m - 1)
BUF_AUTOSAVE_MODIFF (current_buffer) = m;
}
/* The before-change-function may have moved the gap
or even modified the buffer so we should start over. */
goto restart;
}
/* Take care of the case where the new character
combines with neighboring bytes. */
if (maybe_byte_combining
&& (maybe_byte_combining == COMBINING_AFTER
? (pos_byte_next < Z_BYTE
&& ! CHAR_HEAD_P (FETCH_BYTE (pos_byte_next)))
: ((pos_byte_next < Z_BYTE
&& ! CHAR_HEAD_P (FETCH_BYTE (pos_byte_next)))
|| (pos_byte > BEG_BYTE
&& ! ASCII_CHAR_P (FETCH_BYTE (pos_byte - 1))))))
{
Lisp_Object tem, string;
tem = BVAR (current_buffer, undo_list);
/* Make a multibyte string containing this single character. */
string = make_multibyte_string ((char *) tostr, 1, len);
/* replace_range is less efficient, because it moves the gap,
but it handles combining correctly. */
replace_range (pos, pos + 1, string,
false, false, true, false, false);
pos_byte_next = CHAR_TO_BYTE (pos);
if (pos_byte_next > pos_byte)
/* Before combining happened. We should not increment
POS. So, to cancel the later increment of POS,
decrease it now. */
pos--;
else
pos_byte_next += next_char_len (pos_byte_next);
if (! NILP (noundo))
bset_undo_list (current_buffer, tem);
}
else
{
if (NILP (noundo))
record_change (pos, 1);
for (i = 0; i < len; i++) *p++ = tostr[i];
#ifdef HAVE_TREE_SITTER
/* In the previous branch, replace_range() notifies
changes to tree-sitter, but in this branch, we
modified buffer content manually, so we need to
notify tree-sitter manually. */
treesit_record_change (pos_byte, pos_byte + len, pos_byte + len);
#endif
}
last_changed = pos + 1;
}
pos_byte = pos_byte_next;
pos++;
}
if (changed > 0)
{
signal_after_change (changed,
last_changed - changed, last_changed - changed);
update_compositions (changed, last_changed, CHECK_ALL);
}
return unbind_to (count, Qnil);
}