From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!not-for-mail From: Wolfgang Jenkner Newsgroups: gmane.emacs.bugs Subject: bug#12146: 24.1.50; [PATCH] ANSI SGR parameters 22-27 for ansi-color Date: Mon, 06 Aug 2012 15:43:51 +0200 Message-ID: <858vdsgis1.fsf@iznogoud.viz> NNTP-Posting-Host: plane.gmane.org Mime-Version: 1.0 Content-Type: text/plain X-Trace: dough.gmane.org 1344265770 30224 80.91.229.3 (6 Aug 2012 15:09:30 GMT) X-Complaints-To: usenet@dough.gmane.org NNTP-Posting-Date: Mon, 6 Aug 2012 15:09:30 +0000 (UTC) Cc: Alex Schroeder To: 12146@debbugs.gnu.org Original-X-From: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane.org@gnu.org Mon Aug 06 17:09:30 2012 Return-path: Envelope-to: geb-bug-gnu-emacs@m.gmane.org Original-Received: from lists.gnu.org ([208.118.235.17]) by plane.gmane.org with esmtp (Exim 4.69) (envelope-from ) id 1SyOvv-0001TU-MC for geb-bug-gnu-emacs@m.gmane.org; Mon, 06 Aug 2012 17:09:28 +0200 Original-Received: from localhost ([::1]:38610 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1SyOvu-0004sE-RE for geb-bug-gnu-emacs@m.gmane.org; Mon, 06 Aug 2012 11:09:26 -0400 Original-Received: from eggs.gnu.org ([208.118.235.92]:46868) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1SyOvn-0004rs-2I for bug-gnu-emacs@gnu.org; Mon, 06 Aug 2012 11:09:25 -0400 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1SyOvd-0007lt-Oq for bug-gnu-emacs@gnu.org; Mon, 06 Aug 2012 11:09:19 -0400 Original-Received: from debbugs.gnu.org ([140.186.70.43]:56332) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1SyOvd-0007lo-LY for bug-gnu-emacs@gnu.org; Mon, 06 Aug 2012 11:09:09 -0400 Original-Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.72) (envelope-from ) id 1SyP3G-0007Gk-Cz for bug-gnu-emacs@gnu.org; Mon, 06 Aug 2012 11:17:02 -0400 X-Loop: help-debbugs@gnu.org Resent-From: Wolfgang Jenkner Original-Sender: debbugs-submit-bounces@debbugs.gnu.org Resent-CC: bug-gnu-emacs@gnu.org Resent-Date: Mon, 06 Aug 2012 15:17:02 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: report 12146 X-GNU-PR-Package: emacs X-GNU-PR-Keywords: patch X-Debbugs-Original-To: bug-gnu-emacs@gnu.org Original-Received: via spool by submit@debbugs.gnu.org id=B.134426620727916 (code B ref -1); Mon, 06 Aug 2012 15:17:02 +0000 Original-Received: (at submit) by debbugs.gnu.org; 6 Aug 2012 15:16:47 +0000 Original-Received: from localhost ([127.0.0.1]:37645 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.72) (envelope-from ) id 1SyP2z-0007GC-PV for submit@debbugs.gnu.org; Mon, 06 Aug 2012 11:16:46 -0400 Original-Received: from eggs.gnu.org ([208.118.235.92]:52043) by debbugs.gnu.org with esmtp (Exim 4.72) (envelope-from ) id 1SyP2w-0007G3-4v for submit@debbugs.gnu.org; Mon, 06 Aug 2012 11:16:44 -0400 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1SyOvG-0007i8-GQ for submit@debbugs.gnu.org; Mon, 06 Aug 2012 11:08:48 -0400 Original-Received: from lists.gnu.org ([208.118.235.17]:48178) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1SyOvG-0007i4-D0 for submit@debbugs.gnu.org; Mon, 06 Aug 2012 11:08:46 -0400 Original-Received: from eggs.gnu.org ([208.118.235.92]:46748) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1SyOvB-0004en-8q for bug-gnu-emacs@gnu.org; Mon, 06 Aug 2012 11:08:46 -0400 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1SyOv4-0007ec-DC for bug-gnu-emacs@gnu.org; Mon, 06 Aug 2012 11:08:41 -0400 Original-Received: from mx01.lb01.inode.at ([62.99.145.1]:60220 helo=mx.inode.at) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1SyOv3-0007e6-Vm; Mon, 06 Aug 2012 11:08:34 -0400 Original-Received: from [91.119.101.188] (port=8238 helo=iznogoud.viz) by smartmx-01.inode.at with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.69) (envelope-from ) id 1SyOv1-0006Kj-Ro; Mon, 06 Aug 2012 17:08:32 +0200 Original-Received: from wolfgang by iznogoud.viz with local (Exim 4.80 (FreeBSD)) (envelope-from ) id 1SyOv0-0006LT-Aq; Mon, 06 Aug 2012 17:08:30 +0200 User-Agent: Gnus/5.130006 (Ma Gnus v0.6) Emacs/24.1.50 (berkeley-unix) X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.6, seldom 2.4 (older, 4) X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.6 (newer, 3) X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.13 Precedence: list X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.6 (newer, 2) X-Received-From: 140.186.70.43 X-BeenThere: bug-gnu-emacs@gnu.org List-Id: "Bug reports for GNU Emacs, the Swiss army knife of text editors" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane.org@gnu.org Original-Sender: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane.org@gnu.org Xref: news.gmane.org gmane.emacs.bugs:62866 Archived-At: This patch implements ANSI SGR parameters which turn off "graphic rendition aspects" in the 1-7 range. Part of the motivation for this change is using ansi-color to render man pages, see the "ansi-color for man" bug report (and patch) for more details. The patch introduces an incompatible change (more or less behind the scenes): Previously, the car of the ansi-color-context and ansi-color-context-region variables contained a list of faces while it now contains a list of ANSI SGR parameters instead (referred to in the source as "codes"); this also affects the second argument passed to ansi-color-apply-sequence. This is because different parameters could be mapped to the same face. If they are both mapped to `default' this already triggers a bug in the current version of ansi-color: In a shell-mode buffer type printf '\033[4mfoo\033[2mbar\033[m\n' and note that "bar" is not underlined in the output (SGR parameter 2 "faint" is mapped to the `default' face and is therefore treated in the same way as parameter 0). I've run a simple test to check that the new code doesn't slow down ansi-color, viz. ./emacs/src/emacs --batch -Q -l ./ansi-color-test.el where ansi-color-test.el contains the form (progn (require 'ansi-color) (garbage-collect) (let ((ansi-color-apply-face-function (lambda (beg end face) (when face (put-text-property beg end 'face face)))) (file "/tmp/ansi-color-test")) (with-temp-file file (shell-command "PAGER=cat MANPAGER=cat MAN_KEEP_FORMATTING=1 man bash" (current-buffer))) (let ((time (current-time))) (dotimes (i 100) (with-temp-buffer (insert-file file) (ansi-color-apply-on-region (point-min) (point-max)))) (message "%s" (time-subtract (current-time) time))))) 2012-08-02 Wolfgang Jenkner Implement ANSI SGR parameters 22-27. * ansi-color.el (ansi-colors): Doc fix. (ansi-color-context, ansi-color-context-region): Doc fix. (ansi-color--find-face): New function. (ansi-color-apply, ansi-color-apply-on-region): Use it. Rename the local variable `face' to `codes' since it is now a list of ansi codes. Doc fix. (ansi-color-get-face): Remove. (ansi-color-parse-sequence): New function, derived from ansi-color-get-face. (ansi-color-apply-sequence): Use it. Rewrite, and support ansi codes 22-27. In GNU Emacs 24.1.50.1 (amd64-unknown-freebsd9.0, GTK+ Version 2.24.6) of 2012-07-20 on iznogoud.viz Windowing system distributor `The X.Org Foundation', version 11.0.11006000 Configured using: `configure '--prefix=/opt' '--without-gsettings' 'MAKE=gmake'' Important settings: value of $LC_CTYPE: en_US.UTF-8 locale-coding-system: utf-8-unix default enable-multibyte-characters: t === modified file 'lisp/ansi-color.el' --- lisp/ansi-color.el 2012-06-23 09:28:10 +0000 +++ lisp/ansi-color.el 2012-08-05 13:24:19 +0000 @@ -83,7 +83,7 @@ "Translating SGR control sequences to faces. This translation effectively colorizes strings and regions based upon SGR control sequences embedded in the text. SGR (Select Graphic -Rendition) control sequences are defined in section 3.8.117 of the +Rendition) control sequences are defined in section 8.3.117 of the ECMA-48 standard \(identical to ISO/IEC 6429), which is freely available as a PDF file ." :version "21.1" @@ -236,9 +236,10 @@ ;; Working with strings (defvar ansi-color-context nil "Context saved between two calls to `ansi-color-apply'. -This is a list of the form (FACES FRAGMENT) or nil. FACES is a list of -faces the last call to `ansi-color-apply' ended with, and FRAGMENT is a -string starting with an escape sequence, possibly the start of a new +This is a list of the form (CODES FRAGMENT) or nil. CODES +represents the state the last call to `ansi-color-apply' ended +with, currently a list of ansi codes, and FRAGMENT is a string +starting with an escape sequence, possibly the start of a new escape sequence.") (make-variable-buffer-local 'ansi-color-context) @@ -270,6 +271,20 @@ (setq ansi-color-context (if fragment (list nil fragment)))) result)) +(defun ansi-color--find-face (codes) + "Return the face corresponding to CODES." + (let (faces) + (while codes + (let ((face (ansi-color-get-face-1 (pop codes)))) + ;; In the (default underline) face, say, the value of the + ;; "underline" attribute of the `default' face wins. + (unless (eq face 'default) + (push face faces)))) + ;; Avoid some long-lived conses in the common case. + (if (cdr faces) + (nreverse faces) + (car faces)))) + (defun ansi-color-apply (string) "Translates SGR control sequences into text properties. Delete all other control sequences without processing them. @@ -280,12 +295,12 @@ See function `ansi-color-apply-sequence' for details. Every call to this function will set and use the buffer-local variable -`ansi-color-context' to save partial escape sequences and current face. +`ansi-color-context' to save partial escape sequences and current ansi codes. This information will be used for the next call to `ansi-color-apply'. Set `ansi-color-context' to nil if you don't want this. This function can be added to `comint-preoutput-filter-functions'." - (let ((face (car ansi-color-context)) + (let ((codes (car ansi-color-context)) (start 0) end escape-sequence result colorized-substring) ;; If context was saved and is a string, prepend it. @@ -296,8 +311,8 @@ (while (setq end (string-match ansi-color-regexp string start)) (setq escape-sequence (match-string 1 string)) ;; Colorize the old block from start to end using old face. - (when face - (put-text-property start end 'font-lock-face face string)) + (when codes + (put-text-property start end 'font-lock-face (ansi-color--find-face codes) string)) (setq colorized-substring (substring string start end) start (match-end 0)) ;; Eliminate unrecognized ANSI sequences. @@ -306,10 +321,10 @@ (replace-match "" nil nil colorized-substring))) (push colorized-substring result) ;; Create new face, by applying escape sequence parameters. - (setq face (ansi-color-apply-sequence escape-sequence face))) + (setq codes (ansi-color-apply-sequence escape-sequence codes))) ;; if the rest of the string should have a face, put it there - (when face - (put-text-property start (length string) 'font-lock-face face string)) + (when codes + (put-text-property start (length string) 'font-lock-face (ansi-color--find-face codes) string)) ;; save context, add the remainder of the string to the result (let (fragment) (if (string-match "\033" string start) @@ -317,17 +332,18 @@ (setq fragment (substring string pos)) (push (substring string start pos) result)) (push (substring string start) result)) - (setq ansi-color-context (if (or face fragment) (list face fragment)))) + (setq ansi-color-context (if (or codes fragment) (list codes fragment)))) (apply 'concat (nreverse result)))) ;; Working with regions (defvar ansi-color-context-region nil "Context saved between two calls to `ansi-color-apply-on-region'. -This is a list of the form (FACES MARKER) or nil. FACES is a list of -faces the last call to `ansi-color-apply-on-region' ended with, and -MARKER is a buffer position within an escape sequence or the last -position processed.") +This is a list of the form (CODES MARKER) or nil. CODES +represents the state the last call to `ansi-color-apply-on-region' +ended with, currently a list of ansi codes, and MARKER is a +buffer position within an escape sequence or the last position +processed.") (make-variable-buffer-local 'ansi-color-context-region) (defun ansi-color-filter-region (begin end) @@ -365,13 +381,14 @@ in `ansi-color-faces-vector' and `ansi-color-names-vector'. See `ansi-color-apply-sequence' for details. -Every call to this function will set and use the buffer-local variable -`ansi-color-context-region' to save position and current face. This -information will be used for the next call to -`ansi-color-apply-on-region'. Specifically, it will override BEGIN, the -start of the region and set the face with which to start. Set -`ansi-color-context-region' to nil if you don't want this." - (let ((face (car ansi-color-context-region)) +Every call to this function will set and use the buffer-local +variable `ansi-color-context-region' to save position and current +ansi codes. This information will be used for the next call to +`ansi-color-apply-on-region'. Specifically, it will override +BEGIN, the start of the region and set the face with which to +start. Set `ansi-color-context-region' to nil if you don't want +this." + (let ((codes (car ansi-color-context-region)) (start-marker (or (cadr ansi-color-context-region) (copy-marker begin))) (end-marker (copy-marker end)) @@ -388,28 +405,27 @@ ;; Colorize the old block from start to end using old face. (funcall ansi-color-apply-face-function start-marker (match-beginning 0) - face) + (ansi-color--find-face codes)) ;; store escape sequence and new start position (setq escape-sequence (match-string 1) start-marker (copy-marker (match-end 0))) ;; delete the escape sequence (replace-match "") - ;; create new face by applying all the parameters in the escape - ;; sequence - (setq face (ansi-color-apply-sequence escape-sequence face))) + ;; Update the list of ansi codes. + (setq codes (ansi-color-apply-sequence escape-sequence codes))) ;; search for the possible start of a new escape sequence (if (re-search-forward "\033" end-marker t) (progn ;; if the rest of the region should have a face, put it there (funcall ansi-color-apply-face-function - start-marker (point) face) - ;; save face and point + start-marker (point) (ansi-color--find-face codes)) + ;; save codes and point (setq ansi-color-context-region - (list face (copy-marker (match-beginning 0))))) + (list codes (copy-marker (match-beginning 0))))) ;; if the rest of the region should have a face, put it there (funcall ansi-color-apply-face-function - start-marker end-marker face) - (setq ansi-color-context-region (if face (list face))))))) + start-marker end-marker (ansi-color--find-face codes)) + (setq ansi-color-context-region (if codes (list codes))))))) (defun ansi-color-apply-overlay-face (beg end face) "Make an overlay from BEG to END, and apply face FACE. @@ -497,32 +513,56 @@ ;; Helper functions -(defun ansi-color-apply-sequence (escape-sequence faces) - "Apply ESCAPE-SEQ to FACES and return the new list of faces. - -ESCAPE-SEQ is an escape sequences parsed by `ansi-color-get-face'. - -If the new faces start with the symbol `default', then the new -faces are returned. If the faces start with something else, -they are appended to the front of the FACES list, and the new -list of faces is returned. - -If `ansi-color-get-face' returns nil, then we either got a -null-sequence, or we stumbled upon some garbage. In either -case we return nil." - (let ((new-faces (ansi-color-get-face escape-sequence))) - (cond ((null new-faces) - nil) - ((eq (car new-faces) 'default) - (cdr new-faces)) - (t - ;; Like (append NEW-FACES FACES) - ;; but delete duplicates in FACES. - (let ((modified-faces (copy-sequence faces))) - (dolist (face (nreverse new-faces)) - (setq modified-faces (delete face modified-faces)) - (push face modified-faces)) - modified-faces))))) +(defsubst ansi-color-parse-sequence (escape-seq) + "Return the list of all the parameters in ESCAPE-SEQ. + +ESCAPE-SEQ is a SGR control sequences such as \\033[34m. The parameter +34 is used by `ansi-color-get-face-1' to return a face definition. + +Returns nil only if there's no match for `ansi-color-parameter-regexp'." + (let ((i 0) + codes val) + (while (string-match ansi-color-parameter-regexp escape-seq i) + (setq i (match-end 0) + val (string-to-number (match-string 1 escape-seq) 10)) + ;; It so happens that (string-to-number "") => 0. + (push val codes)) + (nreverse codes))) + +(defun ansi-color-apply-sequence (escape-sequence codes) + "Apply ESCAPE-SEQ to CODES and return the new list of codes. + +ESCAPE-SEQ is an escape sequence parsed by `ansi-color-parse-sequence'. + +If the new codes resulting from ESCAPE-SEQ start with 0, then the +old codes are discarded and the remaining new codes are +processed. Otherwise, for each new code: if it is 21-25 or 27-29 +delete appropriate parameters from the list of codes; any other +code that makes sense is added to the list of codes. Finally, +the so changed list of codes is returned." + (let ((new-codes (ansi-color-parse-sequence escape-sequence))) + (while new-codes + (setq codes + (let ((new (pop new-codes))) + (cond ((zerop new) + nil) + ((or (<= new 20) + (>= new 30)) + (if (memq new codes) + codes + (cons new codes))) + ;; The standard says `21 doubly underlined' while + ;; http://en.wikipedia.org/wiki/ANSI_escape_code claims + ;; `21 Bright/Bold: off or Underline: Double'. + ((/= new 26) + (remq (- new 20) + (cond ((= new 22) + (remq 1 codes)) + ((= new 25) + (remq 6 codes)) + (t codes)))) + (t codes))))) + codes)) (defun ansi-color-make-color-map () "Creates a vector of face definitions and returns it. @@ -588,28 +628,6 @@ (aref ansi-color-map ansi-code) (args-out-of-range nil))) -(defun ansi-color-get-face (escape-seq) - "Create a new face by applying all the parameters in ESCAPE-SEQ. - -Should any of the parameters result in the default face (usually this is -the parameter 0), then the effect of all previous parameters is canceled. - -ESCAPE-SEQ is a SGR control sequences such as \\033[34m. The parameter -34 is used by `ansi-color-get-face-1' to return a face definition." - (let ((i 0) - f val) - (while (string-match ansi-color-parameter-regexp escape-seq i) - (setq i (match-end 0) - val (ansi-color-get-face-1 - (string-to-number (match-string 1 escape-seq) 10))) - (cond ((not val)) - ((eq val 'default) - (setq f (list val))) - (t - (unless (member val f) - (push val f))))) - f)) - (provide 'ansi-color) ;;; ansi-color.el ends here