From 87e422f1044068a4d27e5e4bfdbc664d9e4bbc43 Mon Sep 17 00:00:00 2001 From: Lars Ingebrigtsen Date: Mon, 21 Dec 2020 18:53:32 +0100 Subject: Beef up the Emacs string utility set a bit * doc/lispref/strings.texi (Modifying Strings): Document them. * lisp/emacs-lisp/shortdoc.el (string): Add examples. * lisp/emacs-lisp/subr-x.el (string-clean-whitespace) (string-fill, string-limit, string-lines, slice-string): New functions. --- lisp/emacs-lisp/subr-x.el | 53 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) (limited to 'lisp/emacs-lisp/subr-x.el') diff --git a/lisp/emacs-lisp/subr-x.el b/lisp/emacs-lisp/subr-x.el index e6abb39ddc6..41a20795378 100644 --- a/lisp/emacs-lisp/subr-x.el +++ b/lisp/emacs-lisp/subr-x.el @@ -264,6 +264,59 @@ carriage return." (substring string 0 (- (length string) (length suffix))) string)) +(defun string-clean-whitespace (string) + "Clean up whitespace in STRING. +All sequences of whitespaces in STRING are collapsed into a +single space character, and leading/trailing whitespace is +removed." + (string-trim (replace-regexp-in-string "[ \t\n\r]+" " " string))) + +(defun string-fill (string length) + "Try to word-wrap STRING so that no lines are longer than LENGTH. +Wrapping is done where there is whitespace. If there are +individual words in STRING that are longer than LENGTH, the +result will have lines that are longer than LENGTH." + (with-temp-buffer + (insert string) + (goto-char (point-min)) + (let ((fill-column length) + (adaptive-fill-mode nil)) + (fill-region (point-min) (point-max))) + (buffer-string))) + +(defun string-limit (string length) + "Return (up to) a LENGTH substring of STRING. +If STRING is shorter or equal to LENGTH, the entire string is +returned unchanged. If STRING is longer than LENGTH, and LENGTH +is a positive number, return a a substring consisting of the +first LENGTH characters of STRING. If LENGTH is negative, return +a substring consisitng of thelast LENGTH characters of STRING." + (cond + ((<= (length string) length) string) + ((>= length 0) (substring string 0 length)) + (t (substring string (+ (length string) length))))) + +(defun string-lines (string &optional omit-nulls) + "Split STRING into a list of lines. +If OMIT-NULLS, empty lines will be removed from the results." + (split-string string "\n" omit-nulls)) + +(defun slice-string (string regexp) + "Split STRING at REGEXP boundaries and return a list of slices. +The boundaries that match REGEXP are not omitted from the results." + (let ((start-substring 0) + (start-search 0) + (result nil)) + (save-match-data + (while (string-match regexp string start-search) + (if (zerop (match-beginning 0)) + (setq start-search (match-end 0)) + (push (substring string start-substring (match-beginning 0)) result) + (setq start-substring (match-beginning 0) + start-search (match-end 0)))) + (push (substring string start-substring) result) + (nreverse result)))) + (defun replace-region-contents (beg end replace-fn &optional max-secs max-costs) "Replace the region between BEG and END using REPLACE-FN. -- cgit v1.2.3 From b3dec3176673fa99e57e3916b36ea4367d47c0fa Mon Sep 17 00:00:00 2001 From: Lars Ingebrigtsen Date: Mon, 21 Dec 2020 20:01:28 +0100 Subject: Add `string-pad' * doc/lispref/strings.texi (Creating Strings): Document it. * lisp/emacs-lisp/shortdoc.el (string): Add example. * lisp/emacs-lisp/subr-x.el (string-pad): New function. --- doc/lispref/strings.texi | 9 +++++++++ etc/NEWS | 2 +- lisp/emacs-lisp/shortdoc.el | 4 ++++ lisp/emacs-lisp/subr-x.el | 20 ++++++++++++++++++++ test/lisp/emacs-lisp/subr-x-tests.el | 6 ++++++ 5 files changed, 40 insertions(+), 1 deletion(-) (limited to 'lisp/emacs-lisp/subr-x.el') diff --git a/doc/lispref/strings.texi b/doc/lispref/strings.texi index e4ca2617512..958ae4c0a15 100644 --- a/doc/lispref/strings.texi +++ b/doc/lispref/strings.texi @@ -419,6 +419,15 @@ Split @var{string} into a list of strings on newline boundaries. If @var{omit-nulls}, remove empty lines from the results. @end defun +@defun string-pad string length &optional padding +Pad @var{string} to the be of @var{length} using @var{padding} as the +padding character (defaulting to the space character). If +@var{string} is shorter than @var{length}, no padding is done. If +@var{length} is positive, the padding is done to the end of the +string, and if it's negative, to the start of the string (using the +absolute value). +@end defun + @node Modifying Strings @section Modifying Strings @cindex modifying strings diff --git a/etc/NEWS b/etc/NEWS index 17c6ce61f94..9b4fcd92fc1 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -1443,7 +1443,7 @@ that makes it a valid button. +++ *** A number of new string manipulation functions have been added. 'string-clean-whitespace', 'string-fill', 'string-limit', -'string-limit' and 'slice-string'. +'string-limit', 'string-pad' and 'slice-string'. +++ *** New variable 'current-minibuffer-command'. diff --git a/lisp/emacs-lisp/shortdoc.el b/lisp/emacs-lisp/shortdoc.el index 8b11b57ff7f..3e1476adfc1 100644 --- a/lisp/emacs-lisp/shortdoc.el +++ b/lisp/emacs-lisp/shortdoc.el @@ -131,6 +131,10 @@ There can be any number of :example/:result elements." (mapconcat :eval (mapconcat (lambda (a) (concat "[" a "]")) '("foo" "bar" "zot") " ")) + (string-pad + :eval (string-pad "foo" 5) + :eval (string-pad "foobar" 5) + :eval (string-pad "foo" -5 ?-)) (mapcar :eval (mapcar #'identity "123")) (format diff --git a/lisp/emacs-lisp/subr-x.el b/lisp/emacs-lisp/subr-x.el index 41a20795378..250ba6e6fa2 100644 --- a/lisp/emacs-lisp/subr-x.el +++ b/lisp/emacs-lisp/subr-x.el @@ -317,6 +317,26 @@ The boundaries that match REGEXP are not omitted from the results." (push (substring string start-substring) result) (nreverse result)))) +(defun string-pad (string length &optional padding) + "Pad STRING to LENGTH using PADDING. +If PADDING is nil, the space character is used. If not nil, it +should be a character. + +If STRING is longer than the absolute value of LENGTH, no padding +is done. + +If LENGTH is positive, the padding is done to the end of the +string, and if it's negative, padding is done to the start of the +string." + (if (> (length string) (abs length)) + string + (let ((pad-length (- (abs length) (length string)))) + (concat (and (< length 0) + (make-string pad-length (or padding ?\s))) + string + (and (> length 0) + (make-string pad-length (or padding ?\s))))))) + (defun replace-region-contents (beg end replace-fn &optional max-secs max-costs) "Replace the region between BEG and END using REPLACE-FN. diff --git a/test/lisp/emacs-lisp/subr-x-tests.el b/test/lisp/emacs-lisp/subr-x-tests.el index 949bbb163eb..94ff459869c 100644 --- a/test/lisp/emacs-lisp/subr-x-tests.el +++ b/test/lisp/emacs-lisp/subr-x-tests.el @@ -608,5 +608,11 @@ (should (equal (slice-string "-foo-bar-" "-") '("-foo" "-bar" "-"))) (should (equal (slice-string "ooo" "lala") '("ooo")))) +(ert-deftest subr-string-pad () + (should (equal (string-pad "foo" 5) "foo ")) + (should (equal (string-pad "foo" 5 ?-) "foo--")) + (should (equal (string-pad "foo" -5 ?-) "--foo")) + (should (equal (string-pad "foo" 2 ?-) "foo"))) + (provide 'subr-x-tests) ;;; subr-x-tests.el ends here -- cgit v1.2.3 From cf2e8321850f81d9eb0ebc23a4887f12dc3dbfac Mon Sep 17 00:00:00 2001 From: Lars Ingebrigtsen Date: Mon, 21 Dec 2020 20:18:57 +0100 Subject: Rename slice-string to string-slice * lisp/emacs-lisp/subr-x.el (string-slice): Rename from slice-string. * doc/lispref/strings.texi (Creating Strings): Ditto. --- doc/lispref/strings.texi | 4 ++-- etc/NEWS | 2 +- lisp/emacs-lisp/shortdoc.el | 6 +++--- lisp/emacs-lisp/subr-x.el | 2 +- test/lisp/emacs-lisp/subr-x-tests.el | 10 +++++----- 5 files changed, 12 insertions(+), 12 deletions(-) (limited to 'lisp/emacs-lisp/subr-x.el') diff --git a/doc/lispref/strings.texi b/doc/lispref/strings.texi index 958ae4c0a15..c65d839a028 100644 --- a/doc/lispref/strings.texi +++ b/doc/lispref/strings.texi @@ -381,13 +381,13 @@ The default value of @var{separators} for @code{split-string}. Its usual value is @w{@code{"[ \f\t\n\r\v]+"}}. @end defvar -@defun slice-string string regexp +@defun string-slice string regexp Split @var{string} into a list of strings on @var{regexp} boundaries. As opposed to @code{split-string}, the boundaries are included in the result set: @example -(slice-string " two words " " +") +(string-slice " two words " " +") @result{} (" two" " words" " ") @end example @end defun diff --git a/etc/NEWS b/etc/NEWS index 9b4fcd92fc1..1d50555c8e1 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -1443,7 +1443,7 @@ that makes it a valid button. +++ *** A number of new string manipulation functions have been added. 'string-clean-whitespace', 'string-fill', 'string-limit', -'string-limit', 'string-pad' and 'slice-string'. +'string-limit', 'string-pad' and 'string-slice'. +++ *** New variable 'current-minibuffer-command'. diff --git a/lisp/emacs-lisp/shortdoc.el b/lisp/emacs-lisp/shortdoc.el index 3e1476adfc1..df31b0aaf1f 100644 --- a/lisp/emacs-lisp/shortdoc.el +++ b/lisp/emacs-lisp/shortdoc.el @@ -151,9 +151,9 @@ There can be any number of :example/:result elements." :eval (split-string "foo bar") :eval (split-string "|foo|bar|" "|") :eval (split-string "|foo|bar|" "|" t)) - (slice-string - :eval (slice-string "foo-bar" "-") - :eval (slice-string "foo-bar--zot-" "-+")) + (string-slice + :eval (string-slice "foo-bar" "-") + :eval (string-slice "foo-bar--zot-" "-+")) (string-lines :eval (string-lines "foo\n\nbar") :eval (string-lines "foo\n\nbar" t)) diff --git a/lisp/emacs-lisp/subr-x.el b/lisp/emacs-lisp/subr-x.el index 250ba6e6fa2..db7e75dfd2b 100644 --- a/lisp/emacs-lisp/subr-x.el +++ b/lisp/emacs-lisp/subr-x.el @@ -301,7 +301,7 @@ a substring consisitng of thelast LENGTH characters of STRING." If OMIT-NULLS, empty lines will be removed from the results." (split-string string "\n" omit-nulls)) -(defun slice-string (string regexp) +(defun string-slice (string regexp) "Split STRING at REGEXP boundaries and return a list of slices. The boundaries that match REGEXP are not omitted from the results." (let ((start-substring 0) diff --git a/test/lisp/emacs-lisp/subr-x-tests.el b/test/lisp/emacs-lisp/subr-x-tests.el index 94ff459869c..6ed06d4ce4f 100644 --- a/test/lisp/emacs-lisp/subr-x-tests.el +++ b/test/lisp/emacs-lisp/subr-x-tests.el @@ -602,11 +602,11 @@ (should (equal (string-lines "foo") '("foo"))) (should (equal (string-lines "foo \nbar") '("foo " "bar")))) -(ert-deftest subr-slice-string () - (should (equal (slice-string "foo-bar" "-") '("foo" "-bar"))) - (should (equal (slice-string "foo-bar-" "-") '("foo" "-bar" "-"))) - (should (equal (slice-string "-foo-bar-" "-") '("-foo" "-bar" "-"))) - (should (equal (slice-string "ooo" "lala") '("ooo")))) +(ert-deftest subr-string-slice () + (should (equal (string-slice "foo-bar" "-") '("foo" "-bar"))) + (should (equal (string-slice "foo-bar-" "-") '("foo" "-bar" "-"))) + (should (equal (string-slice "-foo-bar-" "-") '("-foo" "-bar" "-"))) + (should (equal (string-slice "ooo" "lala") '("ooo")))) (ert-deftest subr-string-pad () (should (equal (string-pad "foo" 5) "foo ")) -- cgit v1.2.3 From fd9431dde443471f17ffeebf9628fd9aee154e1b Mon Sep 17 00:00:00 2001 From: Lars Ingebrigtsen Date: Mon, 21 Dec 2020 20:42:17 +0100 Subject: Fix shorter-than-length case for string-limit * lisp/emacs-lisp/subr-x.el (string-limit): Fix shorter-than-length case. --- lisp/emacs-lisp/subr-x.el | 2 +- test/lisp/emacs-lisp/subr-x-tests.el | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'lisp/emacs-lisp/subr-x.el') diff --git a/lisp/emacs-lisp/subr-x.el b/lisp/emacs-lisp/subr-x.el index db7e75dfd2b..05fa16da499 100644 --- a/lisp/emacs-lisp/subr-x.el +++ b/lisp/emacs-lisp/subr-x.el @@ -292,7 +292,7 @@ is a positive number, return a a substring consisting of the first LENGTH characters of STRING. If LENGTH is negative, return a substring consisitng of thelast LENGTH characters of STRING." (cond - ((<= (length string) length) string) + ((<= (length string) (abs length)) string) ((>= length 0) (substring string 0 length)) (t (substring string (+ (length string) length))))) diff --git a/test/lisp/emacs-lisp/subr-x-tests.el b/test/lisp/emacs-lisp/subr-x-tests.el index 6ed06d4ce4f..c655fcf6ead 100644 --- a/test/lisp/emacs-lisp/subr-x-tests.el +++ b/test/lisp/emacs-lisp/subr-x-tests.el @@ -596,6 +596,7 @@ (should (equal (string-limit "foo" 10) "foo")) (should (equal (string-limit "foo" 2) "fo")) (should (equal (string-limit "foo" -2) "oo")) + (should (equal (string-limit "abc" -10) "abc")) (should (equal (string-limit "foo" 0) ""))) (ert-deftest subr-string-lines () -- cgit v1.2.3 From e967ba301857edd15778a018ae716e4e98fa2fa9 Mon Sep 17 00:00:00 2001 From: "Basil L. Contovounesios" Date: Mon, 21 Dec 2020 20:40:12 +0000 Subject: ; Fix recent string utility additions * etc/NEWS: Fix typo. * lisp/emacs-lisp/subr-x.el (string-limit): Fix typos in docstring. Simplify. (string-slice): Improve docstring wording. (string-pad): Simplify. --- etc/NEWS | 2 +- lisp/emacs-lisp/subr-x.el | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) (limited to 'lisp/emacs-lisp/subr-x.el') diff --git a/etc/NEWS b/etc/NEWS index 1d50555c8e1..a6774be8f73 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -1443,7 +1443,7 @@ that makes it a valid button. +++ *** A number of new string manipulation functions have been added. 'string-clean-whitespace', 'string-fill', 'string-limit', -'string-limit', 'string-pad' and 'string-slice'. +'string-lines', 'string-pad' and 'string-slice'. +++ *** New variable 'current-minibuffer-command'. diff --git a/lisp/emacs-lisp/subr-x.el b/lisp/emacs-lisp/subr-x.el index 05fa16da499..78d0b054b35 100644 --- a/lisp/emacs-lisp/subr-x.el +++ b/lisp/emacs-lisp/subr-x.el @@ -286,15 +286,15 @@ result will have lines that are longer than LENGTH." (defun string-limit (string length) "Return (up to) a LENGTH substring of STRING. -If STRING is shorter or equal to LENGTH, the entire string is -returned unchanged. If STRING is longer than LENGTH, and LENGTH -is a positive number, return a a substring consisting of the +If STRING is shorter than or equal to LENGTH, the entire string +is returned unchanged. If STRING is longer than LENGTH, and +LENGTH is a positive number, return a substring consisting of the first LENGTH characters of STRING. If LENGTH is negative, return -a substring consisitng of thelast LENGTH characters of STRING." +a substring consisting of the last LENGTH characters of STRING." (cond ((<= (length string) (abs length)) string) ((>= length 0) (substring string 0 length)) - (t (substring string (+ (length string) length))))) + ((substring string length)))) (defun string-lines (string &optional omit-nulls) "Split STRING into a list of lines. @@ -303,7 +303,7 @@ If OMIT-NULLS, empty lines will be removed from the results." (defun string-slice (string regexp) "Split STRING at REGEXP boundaries and return a list of slices. -The boundaries that match REGEXP are not omitted from the results." +The boundaries that match REGEXP are included in the result." (let ((start-substring 0) (start-search 0) (result nil)) @@ -328,9 +328,9 @@ is done. If LENGTH is positive, the padding is done to the end of the string, and if it's negative, padding is done to the start of the string." - (if (> (length string) (abs length)) - string - (let ((pad-length (- (abs length) (length string)))) + (let ((pad-length (- (abs length) (length string)))) + (if (< pad-length 0) + string (concat (and (< length 0) (make-string pad-length (or padding ?\s))) string -- cgit v1.2.3 From f329a3180ead740bb85e1edfc48ae360a56f7ffd Mon Sep 17 00:00:00 2001 From: Lars Ingebrigtsen Date: Mon, 21 Dec 2020 22:05:37 +0100 Subject: Add string-chop-newline * doc/lispref/strings.texi (Creating Strings): Document it. * lisp/emacs-lisp/subr-x.el (string-chop-newline): Add new function. --- doc/lispref/strings.texi | 4 ++++ etc/NEWS | 2 +- lisp/emacs-lisp/shortdoc.el | 2 ++ lisp/emacs-lisp/subr-x.el | 4 ++++ test/lisp/emacs-lisp/subr-x-tests.el | 5 +++++ 5 files changed, 16 insertions(+), 1 deletion(-) (limited to 'lisp/emacs-lisp/subr-x.el') diff --git a/doc/lispref/strings.texi b/doc/lispref/strings.texi index c65d839a028..17cc1a47124 100644 --- a/doc/lispref/strings.texi +++ b/doc/lispref/strings.texi @@ -428,6 +428,10 @@ string, and if it's negative, to the start of the string (using the absolute value). @end defun +@defun string-chop-newline string +Remove the final newline, if any, from @var{string}. +@end defun + @node Modifying Strings @section Modifying Strings @cindex modifying strings diff --git a/etc/NEWS b/etc/NEWS index a6774be8f73..46b8435a14d 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -1443,7 +1443,7 @@ that makes it a valid button. +++ *** A number of new string manipulation functions have been added. 'string-clean-whitespace', 'string-fill', 'string-limit', -'string-lines', 'string-pad' and 'string-slice'. +'string-lines', 'string-pad', 'string-chop-newline' and 'string-slice'. +++ *** New variable 'current-minibuffer-command'. diff --git a/lisp/emacs-lisp/shortdoc.el b/lisp/emacs-lisp/shortdoc.el index df31b0aaf1f..9bd06636f4d 100644 --- a/lisp/emacs-lisp/shortdoc.el +++ b/lisp/emacs-lisp/shortdoc.el @@ -181,6 +181,8 @@ There can be any number of :example/:result elements." (string-remove-prefix :no-manual t :eval (string-remove-prefix "foo" "foobar")) + (string-chop-newline + :eval (string-chop-newline "foo\n")) (string-clean-whitespace :eval (string-clean-whitespace " foo bar ")) (string-fill diff --git a/lisp/emacs-lisp/subr-x.el b/lisp/emacs-lisp/subr-x.el index 78d0b054b35..80d4cb9b650 100644 --- a/lisp/emacs-lisp/subr-x.el +++ b/lisp/emacs-lisp/subr-x.el @@ -337,6 +337,10 @@ string." (and (> length 0) (make-string pad-length (or padding ?\s))))))) +(defun string-chop-newline (string) + "Remove the final newline (if any) from STRING." + (replace-regexp-in-string "\n\\'" "" string)) + (defun replace-region-contents (beg end replace-fn &optional max-secs max-costs) "Replace the region between BEG and END using REPLACE-FN. diff --git a/test/lisp/emacs-lisp/subr-x-tests.el b/test/lisp/emacs-lisp/subr-x-tests.el index c655fcf6ead..ab5a5bfa641 100644 --- a/test/lisp/emacs-lisp/subr-x-tests.el +++ b/test/lisp/emacs-lisp/subr-x-tests.el @@ -615,5 +615,10 @@ (should (equal (string-pad "foo" -5 ?-) "--foo")) (should (equal (string-pad "foo" 2 ?-) "foo"))) +(ert-deftest subr-string-chop-newline () + (should (equal (string-chop-newline "foo\n") "foo")) + (should (equal (string-chop-newline "foo\nbar\n") "foo\nbar")) + (should (equal (string-chop-newline "foo\nbar") "foo\nbar"))) + (provide 'subr-x-tests) ;;; subr-x-tests.el ends here -- cgit v1.2.3 From 768522750ddbf68eb86b336fb41df9ec2fae6988 Mon Sep 17 00:00:00 2001 From: Lars Ingebrigtsen Date: Mon, 21 Dec 2020 22:41:37 +0100 Subject: Make string-clean-whitespace work on non-ASCII whitespace, too * lisp/emacs-lisp/subr-x.el (string-clean-whitespace): Also clean up non-ASCII whitespace. --- lisp/emacs-lisp/subr-x.el | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'lisp/emacs-lisp/subr-x.el') diff --git a/lisp/emacs-lisp/subr-x.el b/lisp/emacs-lisp/subr-x.el index 80d4cb9b650..4d1a73a251a 100644 --- a/lisp/emacs-lisp/subr-x.el +++ b/lisp/emacs-lisp/subr-x.el @@ -269,7 +269,9 @@ carriage return." All sequences of whitespaces in STRING are collapsed into a single space character, and leading/trailing whitespace is removed." - (string-trim (replace-regexp-in-string "[ \t\n\r]+" " " string))) + (let ((blank "[[:blank:]\n]+")) + (string-trim (replace-regexp-in-string blank " " string) + blank blank))) (defun string-fill (string length) "Try to word-wrap STRING so that no lines are longer than LENGTH. -- cgit v1.2.3 From 7e86d3bb9b61e3e2c2389e66370df037bd8a8f43 Mon Sep 17 00:00:00 2001 From: Lars Ingebrigtsen Date: Mon, 21 Dec 2020 23:18:05 +0100 Subject: Make string-chop-newline more efficient * lisp/emacs-lisp/subr-x.el (string-chop-newline): Make more efficient. --- lisp/emacs-lisp/subr-x.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lisp/emacs-lisp/subr-x.el') diff --git a/lisp/emacs-lisp/subr-x.el b/lisp/emacs-lisp/subr-x.el index 4d1a73a251a..1c8e1d6293f 100644 --- a/lisp/emacs-lisp/subr-x.el +++ b/lisp/emacs-lisp/subr-x.el @@ -341,7 +341,7 @@ string." (defun string-chop-newline (string) "Remove the final newline (if any) from STRING." - (replace-regexp-in-string "\n\\'" "" string)) + (string-remove-suffix "\n" string)) (defun replace-region-contents (beg end replace-fn &optional max-secs max-costs) -- cgit v1.2.3 From 27fab4b140c57a82fac6864bbae0fd9ae1ef363c Mon Sep 17 00:00:00 2001 From: "Basil L. Contovounesios" Date: Mon, 21 Dec 2020 22:34:33 +0000 Subject: Tiny string-clean-whitespace simplification * lisp/emacs-lisp/subr-x.el (string-clean-whitespace): Streamline by treating replacement string as being literal and having fixed case. --- lisp/emacs-lisp/subr-x.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lisp/emacs-lisp/subr-x.el') diff --git a/lisp/emacs-lisp/subr-x.el b/lisp/emacs-lisp/subr-x.el index 1c8e1d6293f..aa39fc1538f 100644 --- a/lisp/emacs-lisp/subr-x.el +++ b/lisp/emacs-lisp/subr-x.el @@ -270,7 +270,7 @@ All sequences of whitespaces in STRING are collapsed into a single space character, and leading/trailing whitespace is removed." (let ((blank "[[:blank:]\n]+")) - (string-trim (replace-regexp-in-string blank " " string) + (string-trim (replace-regexp-in-string blank " " string t t) blank blank))) (defun string-fill (string length) -- cgit v1.2.3 From d2b86118629562600c07dbc5befa78ac8b860b68 Mon Sep 17 00:00:00 2001 From: Lars Ingebrigtsen Date: Tue, 22 Dec 2020 04:24:25 +0100 Subject: Further string-clean-whitespace tweaks * lisp/emacs-lisp/subr-x.el (string-clean-whitespace): Put \r back, which was mistakenly removed. --- lisp/emacs-lisp/subr-x.el | 2 +- test/lisp/emacs-lisp/subr-x-tests.el | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'lisp/emacs-lisp/subr-x.el') diff --git a/lisp/emacs-lisp/subr-x.el b/lisp/emacs-lisp/subr-x.el index aa39fc1538f..6f4f7ed5dce 100644 --- a/lisp/emacs-lisp/subr-x.el +++ b/lisp/emacs-lisp/subr-x.el @@ -269,7 +269,7 @@ carriage return." All sequences of whitespaces in STRING are collapsed into a single space character, and leading/trailing whitespace is removed." - (let ((blank "[[:blank:]\n]+")) + (let ((blank "[[:blank:]\r\n]+")) (string-trim (replace-regexp-in-string blank " " string t t) blank blank))) diff --git a/test/lisp/emacs-lisp/subr-x-tests.el b/test/lisp/emacs-lisp/subr-x-tests.el index ab5a5bfa641..2e16cd0f30b 100644 --- a/test/lisp/emacs-lisp/subr-x-tests.el +++ b/test/lisp/emacs-lisp/subr-x-tests.el @@ -584,7 +584,7 @@ (ert-deftest subr-clean-whitespace () (should (equal (string-clean-whitespace " foo ") "foo")) - (should (equal (string-clean-whitespace " foo \n\t Bar") "foo Bar"))) + (should (equal (string-clean-whitespace " foo \r\n\t  Bar") "foo Bar"))) (ert-deftest subr-string-fill () (should (equal (string-fill "foo" 10) "foo")) -- cgit v1.2.3 From 9480169f1b8a27ed61db0913989c9a81339ccd9d Mon Sep 17 00:00:00 2001 From: Lars Ingebrigtsen Date: Tue, 22 Dec 2020 06:54:32 +0100 Subject: Change the string-limit parameter semantics * lisp/emacs-lisp/subr-x.el (string-limit): Alter the calling convention. --- doc/lispref/strings.texi | 13 ++++++------- lisp/emacs-lisp/shortdoc.el | 2 +- lisp/emacs-lisp/subr-x.el | 19 +++++++++++-------- test/lisp/emacs-lisp/subr-x-tests.el | 7 ++++--- 4 files changed, 22 insertions(+), 19 deletions(-) (limited to 'lisp/emacs-lisp/subr-x.el') diff --git a/doc/lispref/strings.texi b/doc/lispref/strings.texi index 17cc1a47124..80e936e9743 100644 --- a/doc/lispref/strings.texi +++ b/doc/lispref/strings.texi @@ -405,13 +405,12 @@ there are individual words that are longer than @var{length}, these will not be shortened. @end defun -@defun string-limit string length -Return a string that's shorter than @var{length}. If @var{string} is -shorter than @var{length}, @var{string} is returned as is. If -@var{length} is positive, return a substring of @var{string} -consisting of the first @var{length} characters. If @var{length} is -negative, return a string of the @var{-length} last characters -instead. +@defun string-limit string length &optional end +If @var{string} is shorter than @var{length}, @var{string} is returned +as is. Otherwise, return a substring of @var{string} consisting of +the first @var{length} characters. If the optional @var{end} +parameter is given, return a string of the @var{length} last +characters instead. @end defun @defun string-lines string &optional omit-nulls diff --git a/lisp/emacs-lisp/shortdoc.el b/lisp/emacs-lisp/shortdoc.el index 7bb7d233b47..eb57e706608 100644 --- a/lisp/emacs-lisp/shortdoc.el +++ b/lisp/emacs-lisp/shortdoc.el @@ -145,7 +145,7 @@ There can be any number of :example/:result elements." :eval (substring "foobar" 3)) (string-limit :eval (string-limit "foobar" 3) - :eval (string-limit "foobar" -3) + :eval (string-limit "foobar" 3 t) :eval (string-limit "foobar" 10)) (split-string :eval (split-string "foo bar") diff --git a/lisp/emacs-lisp/subr-x.el b/lisp/emacs-lisp/subr-x.el index 6f4f7ed5dce..b79482fd4b3 100644 --- a/lisp/emacs-lisp/subr-x.el +++ b/lisp/emacs-lisp/subr-x.el @@ -286,17 +286,20 @@ result will have lines that are longer than LENGTH." (fill-region (point-min) (point-max))) (buffer-string))) -(defun string-limit (string length) +(defun string-limit (string length &optional end) "Return (up to) a LENGTH substring of STRING. If STRING is shorter than or equal to LENGTH, the entire string -is returned unchanged. If STRING is longer than LENGTH, and -LENGTH is a positive number, return a substring consisting of the -first LENGTH characters of STRING. If LENGTH is negative, return -a substring consisting of the last LENGTH characters of STRING." +is returned unchanged. + +If STRING is longer than LENGTH, return a substring consisting of +the first LENGTH characters of STRING. If END is non-nil, return +the last LENTGH characters instead." + (unless (natnump length) + (signal 'wrong-type-argument (list 'natnump length))) (cond - ((<= (length string) (abs length)) string) - ((>= length 0) (substring string 0 length)) - ((substring string length)))) + ((<= (length string) length) string) + (end (substring string (- (length string) length))) + (t (substring string 0 length)))) (defun string-lines (string &optional omit-nulls) "Split STRING into a list of lines. diff --git a/test/lisp/emacs-lisp/subr-x-tests.el b/test/lisp/emacs-lisp/subr-x-tests.el index 2e16cd0f30b..52b48095149 100644 --- a/test/lisp/emacs-lisp/subr-x-tests.el +++ b/test/lisp/emacs-lisp/subr-x-tests.el @@ -595,9 +595,10 @@ (ert-deftest subr-string-limit () (should (equal (string-limit "foo" 10) "foo")) (should (equal (string-limit "foo" 2) "fo")) - (should (equal (string-limit "foo" -2) "oo")) - (should (equal (string-limit "abc" -10) "abc")) - (should (equal (string-limit "foo" 0) ""))) + (should (equal (string-limit "foo" 2 t) "oo")) + (should (equal (string-limit "abc" 10 t) "abc")) + (should (equal (string-limit "foo" 0) "")) + (should-error (string-limit "foo" -1))) (ert-deftest subr-string-lines () (should (equal (string-lines "foo") '("foo"))) -- cgit v1.2.3 From 051d8f75350e54009180cc2fa5e5f86c92db1e13 Mon Sep 17 00:00:00 2001 From: Lars Ingebrigtsen Date: Tue, 22 Dec 2020 06:59:25 +0100 Subject: Make string-pad take an optional START parameter * lisp/emacs-lisp/subr-x.el (string-pad): Alter the calling convention. --- doc/lispref/strings.texi | 8 ++++---- lisp/emacs-lisp/shortdoc.el | 2 +- lisp/emacs-lisp/subr-x.el | 14 ++++++++------ test/lisp/emacs-lisp/subr-x-tests.el | 2 +- 4 files changed, 14 insertions(+), 12 deletions(-) (limited to 'lisp/emacs-lisp/subr-x.el') diff --git a/doc/lispref/strings.texi b/doc/lispref/strings.texi index 80e936e9743..ef848ac5107 100644 --- a/doc/lispref/strings.texi +++ b/doc/lispref/strings.texi @@ -418,13 +418,13 @@ Split @var{string} into a list of strings on newline boundaries. If @var{omit-nulls}, remove empty lines from the results. @end defun -@defun string-pad string length &optional padding +@defun string-pad string length &optional padding start Pad @var{string} to the be of @var{length} using @var{padding} as the padding character (defaulting to the space character). If @var{string} is shorter than @var{length}, no padding is done. If -@var{length} is positive, the padding is done to the end of the -string, and if it's negative, to the start of the string (using the -absolute value). +@var{start} is @code{nil} (or not present), the padding is done to the +end of the string, and if it's non-@code{nil}, to the start of the +string. @end defun @defun string-chop-newline string diff --git a/lisp/emacs-lisp/shortdoc.el b/lisp/emacs-lisp/shortdoc.el index eb57e706608..e9e1be1d550 100644 --- a/lisp/emacs-lisp/shortdoc.el +++ b/lisp/emacs-lisp/shortdoc.el @@ -134,7 +134,7 @@ There can be any number of :example/:result elements." (string-pad :eval (string-pad "foo" 5) :eval (string-pad "foobar" 5) - :eval (string-pad "foo" -5 ?-)) + :eval (string-pad "foo" 5 ?- t)) (mapcar :eval (mapcar #'identity "123")) (format diff --git a/lisp/emacs-lisp/subr-x.el b/lisp/emacs-lisp/subr-x.el index b79482fd4b3..dc046c3d76a 100644 --- a/lisp/emacs-lisp/subr-x.el +++ b/lisp/emacs-lisp/subr-x.el @@ -322,7 +322,7 @@ The boundaries that match REGEXP are included in the result." (push (substring string start-substring) result) (nreverse result)))) -(defun string-pad (string length &optional padding) +(defun string-pad (string length &optional padding start) "Pad STRING to LENGTH using PADDING. If PADDING is nil, the space character is used. If not nil, it should be a character. @@ -330,16 +330,18 @@ should be a character. If STRING is longer than the absolute value of LENGTH, no padding is done. -If LENGTH is positive, the padding is done to the end of the -string, and if it's negative, padding is done to the start of the +If START is nil (or not present), the padding is done to the end +of the string, and non-nil, padding is done to the start of the string." - (let ((pad-length (- (abs length) (length string)))) + (unless (natnump length) + (signal 'wrong-type-argument (list 'natnump length))) + (let ((pad-length (- length (length string)))) (if (< pad-length 0) string - (concat (and (< length 0) + (concat (and start (make-string pad-length (or padding ?\s))) string - (and (> length 0) + (and (not start) (make-string pad-length (or padding ?\s))))))) (defun string-chop-newline (string) diff --git a/test/lisp/emacs-lisp/subr-x-tests.el b/test/lisp/emacs-lisp/subr-x-tests.el index 52b48095149..854d61ed28e 100644 --- a/test/lisp/emacs-lisp/subr-x-tests.el +++ b/test/lisp/emacs-lisp/subr-x-tests.el @@ -613,7 +613,7 @@ (ert-deftest subr-string-pad () (should (equal (string-pad "foo" 5) "foo ")) (should (equal (string-pad "foo" 5 ?-) "foo--")) - (should (equal (string-pad "foo" -5 ?-) "--foo")) + (should (equal (string-pad "foo" 5 ?- t) "--foo")) (should (equal (string-pad "foo" 2 ?-) "foo"))) (ert-deftest subr-string-chop-newline () -- cgit v1.2.3 From e42a63a96004e2a5865dd3bc48f78f16a879e918 Mon Sep 17 00:00:00 2001 From: "Basil L. Contovounesios" Date: Tue, 22 Dec 2020 09:01:47 +0000 Subject: ; Fix docstrings in last change to subr-x.el --- lisp/emacs-lisp/subr-x.el | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lisp/emacs-lisp/subr-x.el') diff --git a/lisp/emacs-lisp/subr-x.el b/lisp/emacs-lisp/subr-x.el index dc046c3d76a..09c4649817a 100644 --- a/lisp/emacs-lisp/subr-x.el +++ b/lisp/emacs-lisp/subr-x.el @@ -293,7 +293,7 @@ is returned unchanged. If STRING is longer than LENGTH, return a substring consisting of the first LENGTH characters of STRING. If END is non-nil, return -the last LENTGH characters instead." +the last LENGTH characters instead." (unless (natnump length) (signal 'wrong-type-argument (list 'natnump length))) (cond @@ -331,8 +331,8 @@ If STRING is longer than the absolute value of LENGTH, no padding is done. If START is nil (or not present), the padding is done to the end -of the string, and non-nil, padding is done to the start of the -string." +of the string, and if non-nil, padding is done to the start of +the string." (unless (natnump length) (signal 'wrong-type-argument (list 'natnump length))) (let ((pad-length (- length (length string)))) -- cgit v1.2.3 From 22c1f00d997d38ba0c453da5f5e9c526d0ac05b0 Mon Sep 17 00:00:00 2001 From: Lars Ingebrigtsen Date: Wed, 23 Dec 2020 07:45:19 +0100 Subject: Allow string-slice to take zero-length matches * lisp/emacs-lisp/subr-x.el (string-slice): Allow zero-length matches. Code adapted from s.el by Magnar Sveen. --- lisp/emacs-lisp/subr-x.el | 23 ++++++++++------------- test/lisp/emacs-lisp/subr-x-tests.el | 4 +++- 2 files changed, 13 insertions(+), 14 deletions(-) (limited to 'lisp/emacs-lisp/subr-x.el') diff --git a/lisp/emacs-lisp/subr-x.el b/lisp/emacs-lisp/subr-x.el index 09c4649817a..8a9424cbb3d 100644 --- a/lisp/emacs-lisp/subr-x.el +++ b/lisp/emacs-lisp/subr-x.el @@ -308,19 +308,16 @@ If OMIT-NULLS, empty lines will be removed from the results." (defun string-slice (string regexp) "Split STRING at REGEXP boundaries and return a list of slices. -The boundaries that match REGEXP are included in the result." - (let ((start-substring 0) - (start-search 0) - (result nil)) - (save-match-data - (while (string-match regexp string start-search) - (if (zerop (match-beginning 0)) - (setq start-search (match-end 0)) - (push (substring string start-substring (match-beginning 0)) result) - (setq start-substring (match-beginning 0) - start-search (match-end 0)))) - (push (substring string start-substring) result) - (nreverse result)))) +The boundaries that match REGEXP are included in the result. + +Also see `split-string'." + (if (zerop (length string)) + (list "") + (let ((i (string-match-p regexp string 1))) + (if i + (cons (substring string 0 i) + (string-slice (substring string i) regexp)) + (list string))))) (defun string-pad (string length &optional padding start) "Pad STRING to LENGTH using PADDING. diff --git a/test/lisp/emacs-lisp/subr-x-tests.el b/test/lisp/emacs-lisp/subr-x-tests.el index 854d61ed28e..3fc5f1d3ed3 100644 --- a/test/lisp/emacs-lisp/subr-x-tests.el +++ b/test/lisp/emacs-lisp/subr-x-tests.el @@ -608,7 +608,9 @@ (should (equal (string-slice "foo-bar" "-") '("foo" "-bar"))) (should (equal (string-slice "foo-bar-" "-") '("foo" "-bar" "-"))) (should (equal (string-slice "-foo-bar-" "-") '("-foo" "-bar" "-"))) - (should (equal (string-slice "ooo" "lala") '("ooo")))) + (should (equal (string-slice "ooo" "lala") '("ooo"))) + (should (equal (string-slice "foo bar" "\\b") '("foo" " " "bar" ""))) + (should (equal (string-slice "foo bar" "\\b\\|a") '("foo" " " "b" "ar" "")))) (ert-deftest subr-string-pad () (should (equal (string-pad "foo" 5) "foo ")) -- cgit v1.2.3 From 5c86a5329664cd5fd3b81fe991c8d7dc18815e07 Mon Sep 17 00:00:00 2001 From: Lars Ingebrigtsen Date: Wed, 23 Dec 2020 07:59:24 +0100 Subject: Improve the string-limit doc string * lisp/emacs-lisp/subr-x.el (string-limit): Mention truncate-string-to-width in the doc string. --- lisp/emacs-lisp/shortdoc.el | 3 +++ lisp/emacs-lisp/subr-x.el | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) (limited to 'lisp/emacs-lisp/subr-x.el') diff --git a/lisp/emacs-lisp/shortdoc.el b/lisp/emacs-lisp/shortdoc.el index e9e1be1d550..0067495fea0 100644 --- a/lisp/emacs-lisp/shortdoc.el +++ b/lisp/emacs-lisp/shortdoc.el @@ -147,6 +147,9 @@ There can be any number of :example/:result elements." :eval (string-limit "foobar" 3) :eval (string-limit "foobar" 3 t) :eval (string-limit "foobar" 10)) + (truncate-string-to-width + :eval (truncate-string-to-width "foobar" 3) + :eval (truncate-string-to-width "你好bar" 5)) (split-string :eval (split-string "foo bar") :eval (split-string "|foo|bar|" "|") diff --git a/lisp/emacs-lisp/subr-x.el b/lisp/emacs-lisp/subr-x.el index 8a9424cbb3d..7e17a3464e6 100644 --- a/lisp/emacs-lisp/subr-x.el +++ b/lisp/emacs-lisp/subr-x.el @@ -293,7 +293,11 @@ is returned unchanged. If STRING is longer than LENGTH, return a substring consisting of the first LENGTH characters of STRING. If END is non-nil, return -the last LENGTH characters instead." +the last LENGTH characters instead. + +When shortening strings for display purposes, +`truncate-string-to-width' is almost always a better alternative +than this function." (unless (natnump length) (signal 'wrong-type-argument (list 'natnump length))) (cond -- cgit v1.2.3 From 269cec13a2fc6ac18b675d0dadd07a3d4e074a72 Mon Sep 17 00:00:00 2001 From: Lars Ingebrigtsen Date: Fri, 25 Dec 2020 05:16:46 +0100 Subject: Remove `string-slice' -- it's not very well defined * doc/lispref/strings.texi (Creating Strings): Ditto. * lisp/emacs-lisp/subr-x.el (string-slice): Remove. --- doc/lispref/strings.texi | 11 ----------- etc/NEWS | 2 +- lisp/emacs-lisp/shortdoc.el | 3 --- lisp/emacs-lisp/subr-x.el | 13 ------------- test/lisp/emacs-lisp/subr-x-tests.el | 8 -------- 5 files changed, 1 insertion(+), 36 deletions(-) (limited to 'lisp/emacs-lisp/subr-x.el') diff --git a/doc/lispref/strings.texi b/doc/lispref/strings.texi index ef848ac5107..19b91471ed3 100644 --- a/doc/lispref/strings.texi +++ b/doc/lispref/strings.texi @@ -381,17 +381,6 @@ The default value of @var{separators} for @code{split-string}. Its usual value is @w{@code{"[ \f\t\n\r\v]+"}}. @end defvar -@defun string-slice string regexp -Split @var{string} into a list of strings on @var{regexp} boundaries. -As opposed to @code{split-string}, the boundaries are included in the -result set: - -@example -(string-slice " two words " " +") - @result{} (" two" " words" " ") -@end example -@end defun - @defun string-clean-whitespace string Clean up the whitespace in @var{string} by collapsing stretches of whitespace to a single space character, as well as removing all diff --git a/etc/NEWS b/etc/NEWS index b155ff9d42e..3a0102238ca 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -1467,7 +1467,7 @@ This can be set to nil to inhibit hiding passwords in .authinfo files. +++ *** A number of new string manipulation functions have been added. 'string-clean-whitespace', 'string-fill', 'string-limit', -'string-lines', 'string-pad', 'string-chop-newline' and 'string-slice'. +'string-lines', 'string-pad' and 'string-chop-newline'. +++ *** New variable 'current-minibuffer-command'. diff --git a/lisp/emacs-lisp/shortdoc.el b/lisp/emacs-lisp/shortdoc.el index 0067495fea0..618465513da 100644 --- a/lisp/emacs-lisp/shortdoc.el +++ b/lisp/emacs-lisp/shortdoc.el @@ -154,9 +154,6 @@ There can be any number of :example/:result elements." :eval (split-string "foo bar") :eval (split-string "|foo|bar|" "|") :eval (split-string "|foo|bar|" "|" t)) - (string-slice - :eval (string-slice "foo-bar" "-") - :eval (string-slice "foo-bar--zot-" "-+")) (string-lines :eval (string-lines "foo\n\nbar") :eval (string-lines "foo\n\nbar" t)) diff --git a/lisp/emacs-lisp/subr-x.el b/lisp/emacs-lisp/subr-x.el index 7e17a3464e6..dc5840a0865 100644 --- a/lisp/emacs-lisp/subr-x.el +++ b/lisp/emacs-lisp/subr-x.el @@ -310,19 +310,6 @@ than this function." If OMIT-NULLS, empty lines will be removed from the results." (split-string string "\n" omit-nulls)) -(defun string-slice (string regexp) - "Split STRING at REGEXP boundaries and return a list of slices. -The boundaries that match REGEXP are included in the result. - -Also see `split-string'." - (if (zerop (length string)) - (list "") - (let ((i (string-match-p regexp string 1))) - (if i - (cons (substring string 0 i) - (string-slice (substring string i) regexp)) - (list string))))) - (defun string-pad (string length &optional padding start) "Pad STRING to LENGTH using PADDING. If PADDING is nil, the space character is used. If not nil, it diff --git a/test/lisp/emacs-lisp/subr-x-tests.el b/test/lisp/emacs-lisp/subr-x-tests.el index 3fc5f1d3ed3..2ae492ecf15 100644 --- a/test/lisp/emacs-lisp/subr-x-tests.el +++ b/test/lisp/emacs-lisp/subr-x-tests.el @@ -604,14 +604,6 @@ (should (equal (string-lines "foo") '("foo"))) (should (equal (string-lines "foo \nbar") '("foo " "bar")))) -(ert-deftest subr-string-slice () - (should (equal (string-slice "foo-bar" "-") '("foo" "-bar"))) - (should (equal (string-slice "foo-bar-" "-") '("foo" "-bar" "-"))) - (should (equal (string-slice "-foo-bar-" "-") '("-foo" "-bar" "-"))) - (should (equal (string-slice "ooo" "lala") '("ooo"))) - (should (equal (string-slice "foo bar" "\\b") '("foo" " " "bar" ""))) - (should (equal (string-slice "foo bar" "\\b\\|a") '("foo" " " "b" "ar" "")))) - (ert-deftest subr-string-pad () (should (equal (string-pad "foo" 5) "foo ")) (should (equal (string-pad "foo" 5 ?-) "foo--")) -- cgit v1.2.3 From af359de91772478587f768300ca61d64a693fedb Mon Sep 17 00:00:00 2001 From: Lars Ingebrigtsen Date: Fri, 25 Dec 2020 05:58:09 +0100 Subject: Allow `string-limit' to work on encoded strings * doc/lispref/strings.texi (Creating Strings): Document it. * lisp/emacs-lisp/subr-x.el (string-limit): Allow limiting on encoded strings. --- doc/lispref/strings.texi | 9 ++++++++- lisp/emacs-lisp/shortdoc.el | 3 ++- lisp/emacs-lisp/subr-x.el | 34 +++++++++++++++++++++++++++++----- test/lisp/emacs-lisp/subr-x-tests.el | 20 ++++++++++++++++++++ 4 files changed, 59 insertions(+), 7 deletions(-) (limited to 'lisp/emacs-lisp/subr-x.el') diff --git a/doc/lispref/strings.texi b/doc/lispref/strings.texi index 19b91471ed3..1e5f52ddfdd 100644 --- a/doc/lispref/strings.texi +++ b/doc/lispref/strings.texi @@ -394,12 +394,19 @@ there are individual words that are longer than @var{length}, these will not be shortened. @end defun -@defun string-limit string length &optional end +@defun string-limit string length &optional end coding-system If @var{string} is shorter than @var{length}, @var{string} is returned as is. Otherwise, return a substring of @var{string} consisting of the first @var{length} characters. If the optional @var{end} parameter is given, return a string of the @var{length} last characters instead. + +If @var{coding-system} is non-@code{nil}, @var{string} will be encoded +before limiting, and the result will be a unibyte string that's +shorter than @code{length}. If @var{string} contains characters that +are encoded into several bytes (for instance, when using +@code{utf-8}), the resulting unibyte string is never truncated in the +middle of a character representation. @end defun @defun string-lines string &optional omit-nulls diff --git a/lisp/emacs-lisp/shortdoc.el b/lisp/emacs-lisp/shortdoc.el index 618465513da..9d183e0d4e9 100644 --- a/lisp/emacs-lisp/shortdoc.el +++ b/lisp/emacs-lisp/shortdoc.el @@ -146,7 +146,8 @@ There can be any number of :example/:result elements." (string-limit :eval (string-limit "foobar" 3) :eval (string-limit "foobar" 3 t) - :eval (string-limit "foobar" 10)) + :eval (string-limit "foobar" 10) + :eval (string-limit "fo好" 3 nil 'utf-8)) (truncate-string-to-width :eval (truncate-string-to-width "foobar" 3) :eval (truncate-string-to-width "你好bar" 5)) diff --git a/lisp/emacs-lisp/subr-x.el b/lisp/emacs-lisp/subr-x.el index dc5840a0865..9fbb0351af4 100644 --- a/lisp/emacs-lisp/subr-x.el +++ b/lisp/emacs-lisp/subr-x.el @@ -286,7 +286,7 @@ result will have lines that are longer than LENGTH." (fill-region (point-min) (point-max))) (buffer-string))) -(defun string-limit (string length &optional end) +(defun string-limit (string length &optional end coding-system) "Return (up to) a LENGTH substring of STRING. If STRING is shorter than or equal to LENGTH, the entire string is returned unchanged. @@ -295,15 +295,39 @@ If STRING is longer than LENGTH, return a substring consisting of the first LENGTH characters of STRING. If END is non-nil, return the last LENGTH characters instead. +If CODING-SYSTEM is non-nil, STRING will be encoded before +limiting, and LENGTH is interpreted as the number of bytes to +limit the string to. The result will be a unibyte string that is +shorter than LENGTH, but will not contain \"partial\" characters, +even if CODING-SYSTEM encodes characters with several bytes per +character. + When shortening strings for display purposes, `truncate-string-to-width' is almost always a better alternative than this function." (unless (natnump length) (signal 'wrong-type-argument (list 'natnump length))) - (cond - ((<= (length string) length) string) - (end (substring string (- (length string) length))) - (t (substring string 0 length)))) + (if coding-system + (let ((result nil) + (result-length 0) + (index (if end (1- (length string)) 0))) + (while (let ((encoded (encode-coding-char + (aref string index) coding-system))) + (and (<= (+ (length encoded) result-length) length) + (progn + (push encoded result) + (cl-incf result-length (length encoded)) + (setq index (if end (1- index) + (1+ index)))) + (if end (> index -1) + (< index (length string))))) + ;; No body. + ) + (apply #'concat (if end result (nreverse result)))) + (cond + ((<= (length string) length) string) + (end (substring string (- (length string) length))) + (t (substring string 0 length))))) (defun string-lines (string &optional omit-nulls) "Split STRING into a list of lines. diff --git a/test/lisp/emacs-lisp/subr-x-tests.el b/test/lisp/emacs-lisp/subr-x-tests.el index 2ae492ecf15..b17185ab0d3 100644 --- a/test/lisp/emacs-lisp/subr-x-tests.el +++ b/test/lisp/emacs-lisp/subr-x-tests.el @@ -600,6 +600,26 @@ (should (equal (string-limit "foo" 0) "")) (should-error (string-limit "foo" -1))) +(ert-deftest subr-string-limit-coding () + (should (not (multibyte-string-p (string-limit "foó" 10 nil 'utf-8)))) + (should (equal (string-limit "foó" 10 nil 'utf-8) "fo\303\263")) + (should (equal (string-limit "foó" 3 nil 'utf-8) "fo")) + (should (equal (string-limit "foó" 4 nil 'utf-8) "fo\303\263")) + (should (equal (string-limit "foóa" 4 nil 'utf-8) "fo\303\263")) + (should (equal (string-limit "foóá" 4 nil 'utf-8) "fo\303\263")) + (should (equal (string-limit "foóa" 4 nil 'iso-8859-1) "fo\363a")) + (should (equal (string-limit "foóá" 4 nil 'iso-8859-1) "fo\363\341")) + (should (equal (string-limit "foóá" 4 nil 'utf-16) "\376\377\000f")) + + (should (equal (string-limit "foó" 10 t 'utf-8) "fo\303\263")) + (should (equal (string-limit "foó" 3 t 'utf-8) "o\303\263")) + (should (equal (string-limit "foó" 4 t 'utf-8) "fo\303\263")) + (should (equal (string-limit "foóa" 4 t 'utf-8) "o\303\263a")) + (should (equal (string-limit "foóá" 4 t 'utf-8) "\303\263\303\241")) + (should (equal (string-limit "foóa" 4 t 'iso-8859-1) "fo\363a")) + (should (equal (string-limit "foóá" 4 t 'iso-8859-1) "fo\363\341")) + (should (equal (string-limit "foóá" 4 t 'utf-16) "\376\377\000\341"))) + (ert-deftest subr-string-lines () (should (equal (string-lines "foo") '("foo"))) (should (equal (string-lines "foo \nbar") '("foo " "bar")))) -- cgit v1.2.3