From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp2.migadu.com ([2001:41d0:403:4876::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by ms8.migadu.com with LMTPS id +P3SJzffAmb/aQAAe85BDQ:P1 (envelope-from ) for ; Tue, 26 Mar 2024 15:44:07 +0100 Received: from aspmx1.migadu.com ([2001:41d0:403:4876::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp2.migadu.com with LMTPS id +P3SJzffAmb/aQAAe85BDQ (envelope-from ) for ; Tue, 26 Mar 2024 15:44:07 +0100 X-Envelope-To: larch@yhetil.org Authentication-Results: aspmx1.migadu.com; dkim=fail ("body hash did not verify") header.d=gmail.com header.s=20230601 header.b=avl62xdw; spf=pass (aspmx1.migadu.com: domain of "emacs-orgmode-bounces+larch=yhetil.org@gnu.org" designates 209.51.188.17 as permitted sender) smtp.mailfrom="emacs-orgmode-bounces+larch=yhetil.org@gnu.org"; dmarc=fail reason="SPF not aligned (relaxed)" header.from=gmail.com (policy=none) ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=yhetil.org; s=key1; t=1711464247; h=from:from:sender:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:cc:mime-version:mime-version: content-type:content-type:in-reply-to:in-reply-to: references:references:list-id:list-help:list-unsubscribe: list-subscribe:list-post:dkim-signature; bh=D1dHyF7sfmJL1iw5L0IQCyv678aCzywz54duDti/PqU=; b=aAlzk1CZPh88p4Myqdwi1J79TgleUhTXrR1WbQLjF9r1Jswje836DcLE9gTOCvA8PD8cTk eAVpnm9pGXSy6NBL1+UiC+KZOw+wbXGYQHWfGdBon3xea0I8YVsgiZ5/fq02wakeLyhbJJ RLixAoyixqDMFHcMS7RV3JDmkDlkLmWyngehtzlmPy6xRtzYQjfNeJ6u3wD+JYLxjvdTmS 05yhFLb405rtAZEwQmbI0mZQj3P29YidywxwWNYrZXCjtLMLOeT3jWtEQqVrRGv2Nna+1G qFq/DFMFcmfp18PMlU2yZO1qewiAeSVeKFmfMjt3IEYY9EHgW0q4LMHQSi+CCw== ARC-Authentication-Results: i=1; aspmx1.migadu.com; dkim=fail ("body hash did not verify") header.d=gmail.com header.s=20230601 header.b=avl62xdw; spf=pass (aspmx1.migadu.com: domain of "emacs-orgmode-bounces+larch=yhetil.org@gnu.org" designates 209.51.188.17 as permitted sender) smtp.mailfrom="emacs-orgmode-bounces+larch=yhetil.org@gnu.org"; dmarc=fail reason="SPF not aligned (relaxed)" header.from=gmail.com (policy=none) ARC-Seal: i=1; s=key1; d=yhetil.org; t=1711464247; a=rsa-sha256; cv=none; b=p1Icobw0BOy3wUKA4Td5NU+QKgWoTPYjmv4rpGeaE6GzZH1RmPhlBHmbBK1/L9294j8JCR hcAjn5BJHiX2myYAKWi8k3dQCwJ3K19EcYb8Fl+mAGo6HPO6+deYC9mnAuYEeJp9tUZ2IT qWvAAlEks/sCm4wKLePOei28iTCu5V0098c1u9Sd/dBg4aniyt7WHcB0hn1mVoiKETLNnC YtogXwXKHXzZPWH0xgrwZNYR9QXBVZ3ngy1yIhek3Nskf9WXKymrx3v+eGBm3ikCjX2hBy C9rSVh6OkhJpi8HkE5VINQxSm9UcZ7d+hpDjOrkJ6umPaJFPz/T6eYlGJ8SfJQ== Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by aspmx1.migadu.com (Postfix) with ESMTPS id 3DF316D88A for ; Tue, 26 Mar 2024 15:44:07 +0100 (CET) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1rp7Wt-0004WN-5q; Tue, 26 Mar 2024 10:11:55 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1rp7Wr-0004WD-7d for emacs-orgmode@gnu.org; Tue, 26 Mar 2024 10:11:53 -0400 Received: from mail-qv1-xf2f.google.com ([2607:f8b0:4864:20::f2f]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1rp7Wn-0005DL-Sp for emacs-orgmode@gnu.org; Tue, 26 Mar 2024 10:11:52 -0400 Received: by mail-qv1-xf2f.google.com with SMTP id 6a1803df08f44-69691093f90so2618326d6.1 for ; Tue, 26 Mar 2024 07:11:49 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1711462307; x=1712067107; darn=gnu.org; h=mime-version:message-id:in-reply-to:date:subject:cc:to:from :user-agent:references:from:to:cc:subject:date:message-id:reply-to; bh=ag0dIdnUbJDCkDNZCf6BeyFUzkovaPrVpi5eUCOCx3Y=; b=avl62xdwX8Ax3XzMJFXnXz0p3iOyMjgA7BQRzEh/gvnUwV0R5vGwfBWLUaYanFzKku 3EmkJt5ReFu19vYHK7OKz90D9GZrKXnSVnqfhhBnJGkfC0n6hs55e5q7JG6uQWojNsTp V928sqTlMymNJABL9Fa9WXizUSsZduBKpW/H2+45Pdaq8x87oI7RvKxqNC9LywbhyRlG 4I5OKLsESTUZMlHvlUX9H+uoXDQhEMHEmyFEvaoxNasSoWjFJVcT1tdTpfWKiZ6YFBV1 JbCrJi4yg1+x6LlNU6t55zh0ezRiZCEe+RkWqik1TIrxvKwILNq5DnDb20uWxtT4IqIW 4ncA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1711462307; x=1712067107; h=mime-version:message-id:in-reply-to:date:subject:cc:to:from :user-agent:references:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=ag0dIdnUbJDCkDNZCf6BeyFUzkovaPrVpi5eUCOCx3Y=; b=LcpTQGlLBEXRZZEEzttGs/LSED21WuIt3NiXqe7UKwF/+x0u1qGTOwXCQBhcxm1tC/ JQKKkHR8rCrEHJ1csJqcYN1fAY5eMKvcW44EqRNItPNnlU/kpYASibBihLTYc+DWbk4W kLUDneelBAX5H6iJzrS2Ohlhpbrf478KSGclsV8VIhiUCWuho2dJl2MMdALDrD2cobvr 2QZtgYx9Zf4SoYZ123YJndQon6rKGyCKGE+WG0cUKBZGojp/AmHTgsOMJUdC8S2F8Uie ub+8BFay8/8J/bomBN747aQFevZwXhbGpbsnSQ99zTo+rOeKejTfgoNzLIJAMylnLJdK kOxQ== X-Forwarded-Encrypted: i=1; AJvYcCWI3qj92G0SqcGmz+hSU/TtVtA8WiJ6twQEPpHT+kf/yzNoVnEGflhNKx2looIeCn9mWq2diaUYStzaf5S9vvEOhrpk6Eg= X-Gm-Message-State: AOJu0YzdcyZjkl7O/c1pBrpVcNVPS5pigaP2/R1IO98nfzqPuxSFwj6B p/qDLDT2Bzd/3VyX0jHgSjKPotAZUjG8MbdRIzRmFmjDNyP3IgzFXuzkkXjidD0= X-Google-Smtp-Source: AGHT+IE0W8o5P+rkdyL1XfvMkG2Jqi8p1ECvQQF+EoK3BUozPwJsABXSBqzmbLy8SF6np2cwf6hmvQ== X-Received: by 2002:a05:6214:468a:b0:691:1e65:d56b with SMTP id or10-20020a056214468a00b006911e65d56bmr11750941qvb.6.1711462306970; Tue, 26 Mar 2024 07:11:46 -0700 (PDT) Received: from entropy ([2601:243:283:930::8bd6]) by smtp.gmail.com with ESMTPSA id a17-20020a0cc591000000b006904d35e1c6sm4652832qvj.58.2024.03.26.07.11.45 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 26 Mar 2024 07:11:46 -0700 (PDT) References: <874jpuijpc.fsf@gmail.com> <87y1n6igvo.fsf@localhost> <878rev1q0k.fsf@gmail.com> <877cueonkj.fsf@localhost> <87zg6dez93.fsf@gmail.com> <871qjobhwa.fsf@localhost> <877ct5fzt6.fsf@gmail.com> <87a5y1mnj0.fsf@localhost> <87msvcgjgv.fsf@gmail.com> <87le9wq2dg.fsf@localhost> <8734uwhlhj.fsf@gmail.com> <875xzsjfvo.fsf@localhost> User-agent: mu4e 1.8.13; emacs 30.0.50 From: Nathaniel Nicandro To: Ihor Radchenko Cc: Nathaniel Nicandro , emacs-orgmode Subject: Re: [PATCH] Highlight ANSI sequences in the whole buffer (was [PATCH] ANSI color on example blocks and fixed width elements) Date: Tue, 26 Mar 2024 09:02:18 -0500 In-reply-to: <875xzsjfvo.fsf@localhost> Message-ID: <87plvhf5gf.fsf@gmail.com> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Received-SPF: pass client-ip=2607:f8b0:4864:20::f2f; envelope-from=nathanielnicandro@gmail.com; helo=mail-qv1-xf2f.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: emacs-orgmode@gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: "General discussions about Org-mode." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-orgmode-bounces+larch=yhetil.org@gnu.org Sender: emacs-orgmode-bounces+larch=yhetil.org@gnu.org X-Migadu-Country: US X-Migadu-Flow: FLOW_IN X-Migadu-Queue-Id: 3DF316D88A X-Spam-Score: -3.64 X-Migadu-Spam-Score: -3.64 X-Migadu-Scanner: mx10.migadu.com X-TUID: rHp1Z5G1yED4 --=-=-= Content-Type: text/plain Ihor Radchenko writes: > Nathaniel Nicandro writes: Hello, I've finally implemented a solution to what I've discussed previously, inserting zero width spaces as boundary characters after an ANSI sequence to act as a separator from the text after the sequence. This would handle the scenario where deleting into the end byte of a sequence causes ansi-color to recognize the partially deleted sequence plus the character directly after the end byte to be a new sequence. This looked like the invisible region containing a sequence eating up other characters not intended to be part of the region. So for example, suppose you had a control sequence, ^[[42m, where m is the end byte that says the sequence is a color sequence. Let point be signified by *. If we have ^[[42m*text then deletion into the end byte would result in ^[[42*text t is still a valid end byte so the fontification process will recognized the whole thing as a valid sequence still and the t would then become part of the invisible region containing the sequence. To avoid this from happening I have introduced the rule that any valid sequence shall have a zero width space immediately after it and this space remains in the buffer even on deleting into it with, for example, backward-delete-char. Let the zero width space be signified by |. If we have ^[[42m|*text then deletion into the space would now result in ^[[42*|text i.e., the effect is that the deletion went past the space, leaving it alone, and deleted the end byte of the control sequence. Since the control sequence is no longer valid, due to the space being at the position of the end byte, it becomes visible. If you then insert a valid end byte, e.g. m, then the effect is ^[[42m|*text i.e., point moved past the space character. So the implementation of that rule of maintaining a zero width space after valid sequences and the rules around deleting into the space or insertion in front of a space are the main changes in this patch compared to previous versions. > > I tried to test your newest patch with the example file you provided and > I notice two things that would be nice: > > 1. It is a bit confusing to understand why one or other text is colored > without seeing the escape characters. Some customization like > `org-link-descriptive' and a command like `org-toggle-link-display' > would be nice. I can see some users prefer seeing the escape codes. I've gone ahead and implemented the toggling of the visibility of the escapes sequences. The variable is `org-ansi-hide-sequences` and the function is `org-toggle-ansi-display`. I just used buffer-invisibility-spec for this. > > 2. Using overlays for fontification is problematic. In your example > file, table alignment becomes broken when escape sequences are hidden > inside overlays: > > | [31mcell 1 | cell 2 | > | cell 3 | cell 4 | > > looks like > > | cell 1 | cell 2 | > | cell 3 | cell 4 | > > Using text properties would make table alignment work without > adjustments in the org-table.el code. > I've gone ahead and used text properties instead of overlays. >> One thing I would like to start doing is writing some tests for this >> feature. It would be great if someone could point me to some tests >> that I can peruse so that I can get an idea of how I can go about >> writing some of my own. Also, are there any procedures or things I >> should be aware of when trying to write my own tests? > > Check out testing/README file in the Org repository. > > Unfortunately, we do not yet have any existing tests for font-locking in > Org tests. You may still refer to the files in testing/lisp/ to see some > example tests. > > Also, Emacs has built-in library to help writing font-lock tests - > faceup.el. You may consider using it. Its top comment also contains a > number of references to various tools that could be useful to diagnose > font-locking code. I have not looked into testing this feature yet. Feedback appreciated! --=-=-= Content-Type: text/x-patch; charset=utf-8 Content-Disposition: attachment; filename=0001-Highlight-ANSI-escape-sequences.patch Content-Transfer-Encoding: quoted-printable Content-Description: Patch >From ea2345ab218d3bc9c07452b2171afc1361b74b9d Mon Sep 17 00:00:00 2001 From: Nathaniel Nicandro Date: Tue, 9 May 2023 19:58:11 -0500 Subject: [PATCH] Highlight ANSI escape sequences * etc/ORG-NEWS: Describe the new feature. * lisp/org.el (org-fontify-ansi-sequences): New customization variable and function which does the work of fontifying the sequences. (org-ansi-hide-sequences) (org-ansi-highlightable-elements) (org-ansi-highlightable-objects): New customization variables. (org-ansi--before-command, org-ansi--after-command) (org-ansi--before-control-seq-deletion) (org-ansi--after-control-seq-deletion) (org-ansi-zero-width-space, org-ansi-is-zero-width-space) (org-ansi-new-context, org-ansi-process-region) (org-ansi-process-block, org-ansi-process-paragraph) (org-ansi-process-fixed-width) (org-fontify-ansi-sequences-1) (org-toggle-ansi-display): New functions. (org-ansi--control-seq-positions) (org-ansi--change-pending, org-ansi--point-before-command) (org-ansi--point-after-command, org-ansi--at-zero-width-space-p) (org-ansi--delete-through-space-p): New internal variables. (org-set-font-lock-defaults): Add the `org-fontify-ansi-sequences` function to the font-lock keywords. (org-unfontify-region): Delete ANSI specific overlays. (org-ansi-mode): New minor mode to enable/disable highlighting of the sequences. Enabled in Org buffers by default. --- etc/ORG-NEWS | 18 ++ lisp/org.el | 469 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 486 insertions(+), 1 deletion(-) diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS index ca744b9..378eddf 100644 --- a/etc/ORG-NEWS +++ b/etc/ORG-NEWS @@ -946,6 +946,24 @@ properties, links to headlines in the file can also be= made more robust by using the file id instead of the file path. =20 ** New features + +*** ANSI escape sequences are now highlighted in the whole buffer + +A new customization ~org-fontify-ansi-sequences~ is available which +tells Org to highlight all ANSI sequences in the buffer if non-nil and +the new minor mode ~org-ansi-mode~ is enabled. + +To disable highlighting of the sequences you can either +disable ~org-ansi-mode~ or set ~org-fontify-ansi-sequences~ to ~nil~ +and =3DM-x org-mode-restart RET=3D. Doing the latter will disable +highlighting of sequences in all newly opened Org buffers whereas +doing the former disables highlighting locally to the current buffer. + +The visibility of the ANSI sequences is controlled by the new +customization ~org-ansi-hide-sequences~ which, if non-nil, makes the +regions containing the sequences invisible. The visibility can be +toggled with =3DM-x org-toggle-ansi-display RET=3D. + *** =3Dob-tangle.el=3D: New flag to remove tangle targets before writing =20 When ~org-babel-tangle-remove-file-before-write~ is set to ~t~ the diff --git a/lisp/org.el b/lisp/org.el index 7e3bbf9..8bf189a 100644 --- a/lisp/org.el +++ b/lisp/org.el @@ -81,6 +81,7 @@ (eval-when-compile (require 'gnus-sum)) (require 'calendar) (require 'find-func) (require 'format-spec) +(require 'ansi-color) =20 (condition-case nil (load (concat (file-name-directory load-file-name) @@ -3674,6 +3675,12 @@ (defcustom org-fontify-whole-block-delimiter-line t :group 'org-appearance :type 'boolean) =20 +(defcustom org-fontify-ansi-sequences t + "Non-nil means to highlight ANSI escape sequences." + :group 'org-appearance + :type 'boolean + :package-version '(Org . "9.7")) + (defcustom org-highlight-latex-and-related nil "Non-nil means highlight LaTeX related syntax in the buffer. When non-nil, the value should be a list containing any of the @@ -5686,6 +5693,438 @@ (defun org-fontify-extend-region (beg end _old-len) (cons beg (or (funcall extend "end" "]" 1) end))) (t (cons beg end)))))) =20 +(defcustom org-ansi-highlightable-elements + '(plain-list drawer headline inlinetask + example-block export-block fixed-width paragraph) + "A list of element types that will have ANSI sequences processed." + :type '(list (symbol :tag "Element Type")) + :version "9.7" + :group 'org-appearance) + +(defcustom org-ansi-highlightable-objects + '(bold code export-snippet italic macro + strike-through table-cell underline verbatim) + "A list of object types that will have ANSI sequences processed." + :type '(list (symbol :tag "Object Type")) + :version "9.7" + :group 'org-appearance) + +(defcustom org-ansi-hide-sequences t + "Non-nil means Org hides ANSI sequences." + :type 'boolean + :version "9.7" + :group 'org-appearance) + +(defvar org-ansi--control-seq-positions nil) +(defvar org-ansi--change-pending nil) +(defvar org-ansi--point-after-command nil) +(defvar org-ansi--point-before-command nil) +(defvar org-ansi--at-zero-width-space-p nil) +(defvar org-ansi--delete-through-space-p nil) + +(defun org-ansi--before-command () + (setq org-ansi--point-before-command (point)) + (setq org-ansi--delete-through-space-p nil + org-ansi--at-zero-width-space-p + (and (org-ansi-is-zero-width-space (char-before)) + (get-text-property (1- (point)) 'org-ansi)))) + +(defun org-ansi--after-command () + (setq org-ansi--point-after-command (point)) + (when (and org-ansi--at-zero-width-space-p + (=3D (- org-ansi--point-after-command + org-ansi--point-before-command) + -1) + (not (org-ansi-is-zero-width-space (char-after)))) + (setq org-ansi--delete-through-space-p t)) + (setq org-ansi--at-zero-width-space-p nil)) + +(defun org-ansi--before-control-seq-deletion (beg end) + (unless org-ansi--change-pending + ;; Don't repeat work. This modification hook can be called + ;; multiple times all on the same region being modified, once for + ;; each org-ansi region contained in or overlapping with the + ;; modified region. + (setq org-ansi--change-pending t) + (org-with-wide-buffer + ;; The endpoints of the region being modified are fully contained + ;; within org-ansi marked regions if these are true. Fully + ;; contained in this context means that the point does not lie at + ;; the edge or boundary of an org-ansi marked region. + (let ((beg-boundary + (and (get-text-property beg 'org-ansi) + (get-text-property (max (1- beg) (point-min)) 'org-ansi))) + (end-boundary + (and (get-text-property end 'org-ansi) + (get-text-property (min (1+ end) (point-max)) 'org-ansi)= ))) + (if (and beg-boundary end-boundary + (=3D end (next-single-property-change beg 'org-ansi nil en= d))) + ;; If the region being modified is fully contained in a + ;; single contiguous org-ansi region, save the beginning + ;; position of the ANSI sequence that this modification + ;; will affect. + (push (if (setq beg (previous-single-property-change beg 'org-a= nsi)) + (1+ beg) + (point-min)) + org-ansi--control-seq-positions) + ;; Otherwise the region being modified may have multiple + ;; org-ansi regions in its span. + (when beg-boundary + ;; Save start of sequence. + (push (if (setq beg (previous-single-property-change beg 'org-a= nsi)) + (1+ beg) + (point-min)) + org-ansi--control-seq-positions)) + (when (and end-boundary + (< (1+ end) (point-max))) + ;; Save start of remainder of sequence outside region being + ;; modified. It's a marker since we are mainly concerned + ;; with deletions which will move the start of the + ;; remainder after the change. + (let ((m (make-marker))) + (set-marker m (1+ end)) + (push m org-ansi--control-seq-positions)))))))) + +(defun org-ansi--after-control-seq-deletion (_beg _end _len) + (setq org-ansi--change-pending nil) + ;; Loop over the saved positions to check to see if the ANSI + ;; sequences they corresponded to before the modification are still + ;; valid sequences after the modification. + (when org-ansi--control-seq-positions + ;; When there are saved positions, either the beginning or end or + ;; both of the region being modified was fully contained in an + ;; org-ansi region. If the beginning and end are fully contained + ;; in the same org-ansi region then a partial modification of the + ;; ANSI sequence is taking place and it needs to be seen that the + ;; sequence is still valid. The saved position in this case is + ;; the start of the sequence. If the beginning and end are fully + ;; contained in separate org-ansi regions then there will be a + ;; saved position for both of the regions. The one that fully + ;; contains the beginning of the modified region will be the start + ;; of the sequence whereas the one that fully contains the end of + ;; the modified region will be the beginning of the remainder of + ;; the sequence that lies outside the modified region. + (let (pos) + (save-excursion + (while (setq pos (pop org-ansi--control-seq-positions)) + (goto-char pos) + ;; Typically the position is the start of an ANSI sequence, + ;; but in the case that the end position of the region being + ;; modified was fully contained in an org-ansi region, the + ;; position will be the start of the remainder of the region + ;; that is unaffected by the modification. In this case we + ;; check to see if the modification somehow joined an + ;; earlier org-ansi region to the one being processed. + (when (get-text-property (max (1- pos) (point-min)) 'org-ansi) + (goto-char (previous-single-property-change pos 'org-ansi nil = (point-min))) + (unless (get-text-property (point) 'org-ansi) + (forward-char)) + (setq pos (point))) + (unless (re-search-forward + ansi-color-control-seq-regexp + (next-single-property-change (point) 'org-ansi nil (poi= nt-max)) + 'noerror) + (unless (get-text-property (point) 'org-ansi) + (backward-char)) + (when (<=3D pos org-ansi--point-after-command (point)) + ;; Disable adjustment of point when the sequence is no + ;; longer valid so that point does not move to the edge + ;; of the invisible region before making it visible + ;; again due to it not being a valid sequence. + (setq disable-point-adjustment t)) + ;; No need to remove the org-ansi property since that is + ;; handled by font-lock. We remove the modification hooks + ;; since the region is no longer a valid ANSI sequence. + (remove-text-properties + pos (point) '(modification-hooks t)))))))) + +(defun org-ansi-new-context (pos) + "Return a new ANSI context. +An ANSI context has the structure defined in +`ansi-color-context-region'." + (list (list (make-bool-vector 8 nil) + nil nil) + (copy-marker pos))) + +(defun org-ansi-zero-width-space () + "Return an invisible zero width space as a propertized string." + (propertize "=E2=80=8B" 'invisible 'org-ansi 'org-ansi t + 'modification-hooks + (list #'org-ansi--before-control-seq-deletion))) + +(defun org-ansi-is-zero-width-space (c) + "Return non-nil if C is a zero-width space." + (eq c ?=E2=80=8B)) + +(defun org-ansi-process-region (beg end &optional context) + (let ((adjust-point + (lambda (pos) + (letrec ((buf (current-buffer)) + (move + (lambda (_window) + (when (eq (current-buffer) buf) + (goto-char pos) + (remove-hook 'pre-redisplay-functions move))))) + (add-hook 'pre-redisplay-functions move))))) + ;; Handle the case when deleting backward into a zero width space. + ;; What we want to happen is that the deletion goes through the + ;; space and deletes the previous character as well so that the + ;; effect is as if the zero width space wasn't present before the + ;; deletion. + (when (and org-ansi--delete-through-space-p + (<=3D beg org-ansi--point-after-command end)) + (save-excursion + (goto-char org-ansi--point-after-command) + (delete-char -1) + (insert "=E2=80=8B") + (funcall adjust-point (1- (point))))) + ;; Apply the colors. + (or context (setq context (org-ansi-new-context beg))) + (move-marker (cadr context) beg) + (let ((ansi-color-context-region context) + (ansi-color-apply-face-function + (lambda (beg end face) + (when face + (font-lock-prepend-text-property beg end 'face face) + (add-text-properties beg end '(font-lock-multiline t)))))) + (ansi-color-apply-on-region beg end t)) + ;; Make adjustments to the regions containing the sequences. + (save-excursion + (goto-char beg) + (let ((mend (set-marker (make-marker) end))) + (while (re-search-forward ansi-color-control-seq-regexp mend t) + (let ((beg (match-beginning 0)) + (end (point))) + (dolist (ov (overlays-at beg)) + (when (and (=3D beg (overlay-start ov)) + (=3D end (overlay-end ov)) + (overlay-get ov 'invisible)) + ;; Assume this is the overlay added by + ;; `ansi-color-apply-on-region' and convert it to a + ;; text property. + (delete-overlay ov) + (add-text-properties + beg end (list 'invisible 'org-ansi 'org-ansi t + 'modification-hooks + (list #'org-ansi--before-control-seq-deleti= on))) + ;; Handle the case when inserting a character such + ;; that it produces a valid sequence and the point + ;; after the insertion command is located in front of + ;; where the zero width space will be inserted. In + ;; that case, point should be moved after the space to + ;; avoid the situation where inserting another + ;; character will cause a separation between the + ;; sequence and the space which will lead to a new + ;; space being inserted after the sequence to maintain + ;; the invariant that a valid sequence shall always + ;; have a space after it. + (when (and (eq (point) org-ansi--point-after-command) + (< org-ansi--point-before-command + org-ansi--point-after-command)) + (funcall adjust-point (1+ (point)))) + ;; Account for zero width spaces already present in + ;; the buffer, e.g. from opening an Org file that has + ;; already had ANSI sequences processed and is then + ;; saved. + (when (org-ansi-is-zero-width-space (char-after)) + (delete-char 1)) + (insert (org-ansi-zero-width-space)))))) + (set-marker mend nil))))) + +(defun org-ansi-process-block (el &optional context) + (let ((beg (org-element-property :begin el)) + (end (org-element-property :end el))) + (save-excursion + (goto-char beg) + (while (org-at-keyword-p) + (forward-line)) + (setq beg (line-beginning-position 2))) + (save-excursion + (goto-char end) + (skip-chars-backward " \t\n") + (setq end (line-beginning-position))) + (org-ansi-process-region beg end context))) + +(defun org-ansi-process-paragraph (el &optional context) + ;; Compute the regions of the paragraph excluding inline + ;; source blocks. + (let ((pend (org-element-property :contents-end el)) beg end) + (push (point) beg) + (while (re-search-forward + "\\