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.

View in manual

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
{
  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 ();
  ptrdiff_t last_changed = 0;
  bool multibyte_p
    = !NILP (BVAR (current_buffer, enable_multibyte_characters));
  int fromc, toc;

#ifdef HAVE_TREE_SITTER
  ptrdiff_t start_char = fix_position (start);
  ptrdiff_t old_end_char = fix_position (end);
  ptrdiff_t start_byte = CHAR_TO_BYTE (start_char);
  ptrdiff_t old_end_byte = CHAR_TO_BYTE (old_end_char);
  struct ts_linecol start_linecol
    = treesit_linecol_maybe (start_char, start_byte,
			     BUF_TS_LINECOL_POINT (current_buffer));
  struct ts_linecol old_end_linecol
    = treesit_linecol_maybe (old_end_char, old_end_byte,
			     BUF_TS_LINECOL_POINT (current_buffer));
#endif

 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");
    }
  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;
	    }

	  if (NILP (noundo))
	    record_change (pos, 1);
	  for (i = 0; i < len; i++) *p++ = tostr[i];

	  last_changed =  pos + 1;
	}
      pos_byte = pos_byte_next;
      pos++;
    }

  if (changed > 0)
    {
#ifdef HAVE_TREE_SITTER
      treesit_record_change (start_byte, old_end_byte, old_end_byte,
			     start_linecol, old_end_linecol, old_end_char);
#endif
      signal_after_change (changed,
			   last_changed - changed, last_changed - changed);
      update_compositions (changed, last_changed, CHECK_ALL);
    }

  return unbind_to (count, Qnil);
}