]> git.eshelyaron.com Git - emacs.git/commitdiff
Add two optional arguments to 'string-width'
authorEli Zaretskii <eliz@gnu.org>
Wed, 14 Apr 2021 08:47:55 +0000 (11:47 +0300)
committerEli Zaretskii <eliz@gnu.org>
Wed, 14 Apr 2021 08:47:55 +0000 (11:47 +0300)
* src/character.c (Fstring_width, lisp_string_width): Accept two
optional arguments FROM and TO, to indicate the substring to be
considered.
(Fstring_width): Add caveats in the doc string about display
features ignored by the function.  (Bug#47712)
* src/character.h (lisp_string_width): Update prototype.
* src/editfns.c (styled_format): Adjust call of lisp_string_width
to its changed signature.

* test/src/character-tests.el (character-test-string-width): New
file with tests for 'string-width'.

* doc/lispref/display.texi (Size of Displayed Text): Document
caveats of using 'string-width'.

* etc/NEWS: Announce the change.

doc/lispref/display.texi
etc/NEWS
src/character.c
src/character.h
src/editfns.c
test/src/character-tests.el [new file with mode: 0644]

index 68d7e827d2667239708ea4c46e8ed5db8de0966d..82684cd2720f89c142435cccdec9fccb5677b29d 100644 (file)
@@ -1965,9 +1965,18 @@ Tables}).  The width of a tab character is usually @code{tab-width}
 (@pxref{Usual Display}).
 @end defun
 
-@defun string-width string
+@defun string-width string &optional from to
 This function returns the width in columns of the string @var{string},
 if it were displayed in the current buffer and the selected window.
+Optional arguments @var{from} and @var{to} specify the substring of
+@var{string} to consider, and are interpreted as in @code{substring}
+(@pxref{Creating Strings}).
+
+The return value is an approximation: it only considers the values
+returned by @code{char-width} for the constituent characters, always
+takes a tab character as taking @code{tab-width} columns, ignores
+display properties and fonts, etc.  For these reasons, we recommend to
+use @code{window-text-pixel-size}, described below, instead.
 @end defun
 
 @defun truncate-string-to-width string width &optional start-column padding ellipsis ellipsis-text-property
index 320827d881eb3628244afb351d780829b93d328f..d4f942bafe3832aeceea6ce117c23d57db8ba06b 100644 (file)
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -2664,6 +2664,11 @@ Modes that use 'truncate-string-to-width' with non-nil, non-string
 argument ELLIPSIS, will now indicate truncation using '…' when
 the selected frame can display it, and using "..." otherwise.
 
++++
+** 'string-width' now accepts two optional arguments FROM and TO.
+This allows to calculate the width of a substring without consing a
+new string.
+
 +++
 ** New command 'make-directory-autoloads'.
 This does the same as the old command 'update-directory-autoloads',
index a599a0355f42321eb4fb4c7f90d30b3f084a4237..41abb83a48bcb4d28f20304196a6a051232c3bed 100644 (file)
@@ -321,28 +321,32 @@ strwidth (const char *str, ptrdiff_t len)
   return c_string_width ((const unsigned char *) str, len, -1, NULL, NULL);
 }
 
-/* Return width of Lisp string STRING when displayed in the current
-   buffer.  The width is measured by how many columns it occupies on
-   the screen while paying attention to compositions.  If PRECISION >
-   0, return the width of longest substring that doesn't exceed
-   PRECISION, and set number of characters and bytes of the substring
-   in *NCHARS and *NBYTES respectively.  */
+/* Return width of a (substring of a) Lisp string STRING when
+   displayed in the current buffer.  The width is measured by how many
+   columns it occupies on the screen while paying attention to
+   compositions.  If PRECISION > 0, return the width of longest
+   substring that doesn't exceed PRECISION, and set number of
+   characters and bytes of the substring in *NCHARS and *NBYTES
+   respectively.  FROM and TO are zero-based character indices
+   that define the substring of STRING to consider.  */
 
 ptrdiff_t
-lisp_string_width (Lisp_Object string, ptrdiff_t precision,
-                  ptrdiff_t *nchars, ptrdiff_t *nbytes)
+lisp_string_width (Lisp_Object string, ptrdiff_t from, ptrdiff_t to,
+                  ptrdiff_t precision, ptrdiff_t *nchars, ptrdiff_t *nbytes)
 {
-  ptrdiff_t len = SCHARS (string);
   /* This set multibyte to 0 even if STRING is multibyte when it
      contains only ascii and eight-bit-graphic, but that's
      intentional.  */
-  bool multibyte = len < SBYTES (string);
+  bool multibyte = SCHARS (string) < SBYTES (string);
   unsigned char *str = SDATA (string);
-  ptrdiff_t i = 0, i_byte = 0;
+  ptrdiff_t i = from, i_byte = from ? string_char_to_byte (string, from) : 0;
+  ptrdiff_t from_byte = i_byte;
   ptrdiff_t width = 0;
   struct Lisp_Char_Table *dp = buffer_display_table ();
 
-  while (i < len)
+  eassert (precision <= 0 || (nchars && nbytes));
+
+  while (i < to)
     {
       ptrdiff_t chars, bytes, thiswidth;
       Lisp_Object val;
@@ -375,8 +379,8 @@ lisp_string_width (Lisp_Object string, ptrdiff_t precision,
 
       if (0 < precision && precision - width < thiswidth)
        {
-         *nchars = i;
-         *nbytes = i_byte;
+         *nchars = i - from;
+         *nbytes = i_byte - from_byte;
          return width;
        }
       if (INT_ADD_WRAPV (thiswidth, width, &width))
@@ -387,27 +391,37 @@ lisp_string_width (Lisp_Object string, ptrdiff_t precision,
 
   if (precision > 0)
     {
-      *nchars = i;
-      *nbytes = i_byte;
+      *nchars = i - from;
+      *nbytes = i_byte - from_byte;
     }
 
   return width;
 }
 
-DEFUN ("string-width", Fstring_width, Sstring_width, 1, 1, 0,
+DEFUN ("string-width", Fstring_width, Sstring_width, 1, 3, 0,
        doc: /* Return width of STRING when displayed in the current buffer.
 Width is measured by how many columns it occupies on the screen.
+Optional arguments FROM and TO specify the substring of STRING to
+consider, and are interpreted as in `substring'.
+
 When calculating width of a multibyte character in STRING,
 only the base leading-code is considered; the validity of
 the following bytes is not checked.  Tabs in STRING are always
-taken to occupy `tab-width' columns.
-usage: (string-width STRING)  */)
-  (Lisp_Object str)
+taken to occupy `tab-width' columns.  The effect of faces and fonts
+used for non-Latin and other unusual characters (such as emoji) is
+ignored as well, as are display properties and invisible text.
+For these reasons, the results are not generally reliable;
+for accurate dimensions of text as it will be displayed,
+use `window-text-pixel-size' instead.
+usage: (string-width STRING &optional FROM TO)  */)
+  (Lisp_Object str, Lisp_Object from, Lisp_Object to)
 {
   Lisp_Object val;
+  ptrdiff_t ifrom, ito;
 
   CHECK_STRING (str);
-  XSETFASTINT (val, lisp_string_width (str, -1, NULL, NULL));
+  validate_subarray (str, from, to, SCHARS (str), &ifrom, &ito);
+  XSETFASTINT (val, lisp_string_width (str, ifrom, ito, -1, NULL, NULL));
   return val;
 }
 
index cbf43097ae2e29da693d40e67c950fcfb34aafab..d19e1e2604c5a650501ff6a2cd6e51fdb3c7eb26 100644 (file)
@@ -572,8 +572,8 @@ extern ptrdiff_t str_to_unibyte (const unsigned char *, unsigned char *,
 extern ptrdiff_t strwidth (const char *, ptrdiff_t);
 extern ptrdiff_t c_string_width (const unsigned char *, ptrdiff_t, int,
                                 ptrdiff_t *, ptrdiff_t *);
-extern ptrdiff_t lisp_string_width (Lisp_Object, ptrdiff_t,
-                                   ptrdiff_t *, ptrdiff_t *);
+extern ptrdiff_t lisp_string_width (Lisp_Object, ptrdiff_t, ptrdiff_t,
+                                   ptrdiff_t, ptrdiff_t *, ptrdiff_t *);
 
 extern Lisp_Object Vchar_unify_table;
 extern Lisp_Object string_escape_byte8 (Lisp_Object);
index 87e743afc314f09a3ec8c796e141c44b5dfad436..bc73c1e2c5bd904c7744258b3a11a28a85ef6c68 100644 (file)
@@ -3386,12 +3386,11 @@ styled_format (ptrdiff_t nargs, Lisp_Object *args, bool message)
              else
                {
                  ptrdiff_t nch, nby;
-                 width = lisp_string_width (arg, prec, &nch, &nby);
+                 nchars_string = SCHARS (arg);
+                 width = lisp_string_width (arg, 0, nchars_string, prec,
+                                            &nch, &nby);
                  if (prec < 0)
-                   {
-                     nchars_string = SCHARS (arg);
-                     nbytes = SBYTES (arg);
-                   }
+                   nbytes = SBYTES (arg);
                  else
                    {
                      nchars_string = nch;
diff --git a/test/src/character-tests.el b/test/src/character-tests.el
new file mode 100644 (file)
index 0000000..10fc4db
--- /dev/null
@@ -0,0 +1,45 @@
+;;; character-tests.el -- tests for character.c  -*- lexical-binding:t -*-
+
+;; Copyright (C) 2021 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.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Code:
+
+(require 'ert)
+
+(ert-deftest character-test-string-width ()
+  "Test `string-width' with and without compositions."
+  (should (= (string-width "1234") 4))
+  (should (= (string-width "12\t34") (+ 4 tab-width)))
+  (should (= (string-width "áëòç") 4))
+  (should (= (string-width "áëòç") 4))
+  (should (= (string-width "הַרְבֵּה אַהֲבָה") 9))
+  (should (= (string-width "1234" 1 3) 2))
+  (should (= (string-width "1234" nil -1) 3))
+  (should (= (string-width "1234" 2) 2))
+  (should-error (string-width "1234" nil 5))
+  (should-error (string-width "1234" -5))
+  (should (= (string-width "12\t34") (+ 4 tab-width)))
+  (should (= (string-width "1234\t56") (+ 6 tab-width)))
+  (should (= (string-width "áëòç") 4))
+  (should (= (string-width "áëòç" nil 3) 3))
+  (should (= (string-width "áëòç" 1 3) 2))
+  (should (= (string-width "áëòç" nil 2) 1))
+  (should (= (string-width "áëòç" nil 3) 2))
+  (should (= (string-width "áëòç" nil 4) 2))
+  (should (= (string-width "הַרְבֵּה אַהֲבָה") 9))
+  (should (= (string-width "הַרְבֵּה אַהֲבָה" nil 8) 4)))