From: Nathaniel Nicandro <nathanielnicandro@gmail.com>
To: Ihor Radchenko <yantar92@posteo.net>
Cc: emacs-orgmode <emacs-orgmode@gnu.org>
Subject: Re: [PATCH] Highlight ANSI sequences in the whole buffer (was [PATCH] ANSI color on example blocks and fixed width elements)
Date: Sun, 01 Dec 2024 02:01:59 -0600 [thread overview]
Message-ID: <87plmbygp4.fsf@gmail.com> (raw)
In-Reply-To: <871pz1apky.fsf@localhost> (Ihor Radchenko's message of "Sat, 23 Nov 2024 16:21:17 +0000")
[-- Attachment #1: Type: text/plain, Size: 330 bytes --]
Ihor Radchenko <yantar92@posteo.net> writes:
> I will need some time to review the patch. It would be helpful for the
> review if all the functions had a docstring and the data structure for
> CONTEXT were described in commentary.
Attached is an updated patch with all functions documented and more
comments about the CONTEXT.
[-- Attachment #2: patch --]
[-- Type: text/x-patch, Size: 52474 bytes --]
From 23706b527f44e5e36f020919df0717ab6005e055 Mon Sep 17 00:00:00 2001
From: Nathaniel Nicandro <nathanielnicandro@gmail.com>
Date: Sun, 17 Nov 2024 16:18:22 -0600
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-highlightable-elements)
(org-ansi-highlightable-objects)
(org-ansi-hide-sequences): New customization variables.
(org-ansi-context, org-ansi-ansi-color-context): New variables.
(org-ansi-new-context, org-ansi-copy-context, org-ansi-null-context-p)
(org-ansi-clear-context, org-ansi-pack-context)
(org-ansi-unpack-to-context, org-ansi-context-contained-p)
(org-ansi-previous-context, org-ansi-point-context)
(org-ansi-result-element)
(org-ansi-highlightable-element-p)
(org-ansi-extent-of-context)
(org-ansi-widened-element-and-end)
(org-ansi-apply-on-region)
(org-ansi-extend-region)
(org-ansi-process-region, org-ansi-process-object)
(org-ansi-process-lines, org-ansi-process-lines-consider-objects)
(org-ansi-process-element)
(org-ansi-visit-elements)
(org-toggle-ansi-display): New functions.
(org-set-font-lock-defaults): Add the `org-fontify-ansi-sequences`
function to the font-lock keywords.
(org-unfontify-region): Remove the `org-ansi-context` property.
(org-ansi-mode): New minor mode to enable/disable highlighting of the
sequences. Enable it in Org buffers by default.
* testing/lisp/test-org.el (faceup): New require.
(test-org/ansi-sequence-fontification):
(test-org/ansi-sequence-editing): New tests.
---
etc/ORG-NEWS | 17 +
lisp/org.el | 743 ++++++++++++++++++++++++++++++++++++++-
testing/lisp/test-org.el | 313 +++++++++++++++++
3 files changed, 1072 insertions(+), 1 deletion(-)
diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index 92bfe35..cd875a8 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -76,6 +76,23 @@ now have diary timestamps included as well.
# We list the most important features, and the features that may
# require user action to be used.
+*** 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 =M-x org-mode-restart RET=. 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 =M-x org-toggle-ansi-display RET=.
+
*** Alignment of image previews can be customized
This is not a new feature. It has been added in Org 9.7, but not
diff --git a/lisp/org.el b/lisp/org.el
index 1e90579..c833027 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -82,6 +82,7 @@ (require 'calendar)
(require 'find-func)
(require 'format-spec)
(require 'thingatpt)
+(require 'ansi-color)
(condition-case nil
(load (concat (file-name-directory load-file-name)
@@ -3688,6 +3689,12 @@ (defcustom org-fontify-whole-block-delimiter-line t
:group 'org-appearance
:type 'boolean)
+(defcustom org-fontify-ansi-sequences t
+ "Non-nil means to highlight ANSI escape sequences."
+ :group 'org-appearance
+ :type 'boolean
+ :package-version '(Org . "9.8"))
+
(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
@@ -5627,6 +5634,715 @@ (defun org-fontify-extend-region (beg end _old-len)
(cons beg (or (funcall extend "end" "]" 1) end)))
(t (cons beg end))))))
+(defcustom org-ansi-highlightable-elements
+ '(plain-list drawer headline inlinetask table
+ table-row paragraph example-block export-block fixed-width)
+ "A list of element types that will have ANSI sequences highlighted.
+ANSI sequences in elements not in this list will not be highlighted."
+ :type '(list (symbol :tag "Element Type"))
+ :package-version '(Org . "9.8")
+ :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 highlighted.
+ANSI sequences in objects not in this list will not be highlighted."
+ :type '(list (symbol :tag "Object Type"))
+ :package-version '(Org . "9.8")
+ :group 'org-appearance)
+
+(defcustom org-ansi-hide-sequences nil
+ "Non-nil means Org hides ANSI sequences."
+ :type 'boolean
+ :package-version '(Org . "9.8")
+ :group 'org-appearance)
+
+(defvar org-ansi-context nil
+ "The ANSI color context for the buffer.
+An Org ANSI context is the same as the FACE-VEC structure defined
+in `ansi-color-context-region', i.e. a list of the form
+
+ (BASIC-FACES FG BG)
+
+where BASIC-FACES is a `bool-vector' and FG and BG integers
+representing the foreground and background colors of the context
+or nil.")
+(make-variable-buffer-local 'org-ansi-context)
+
+(defun org-ansi-new-context ()
+ "Return a new ANSI context.
+See `org-ansi-context'."
+ (list (make-bool-vector 8 nil) nil nil))
+
+(defun org-ansi-copy-context (context)
+ "Return a copy of CONTEXT.
+See `org-ansi-context'."
+ (let ((basic-faces (make-bool-vector 8 nil)))
+ (bool-vector-union basic-faces (car context) basic-faces)
+ (list basic-faces
+ (cadr context)
+ (caddr context))))
+
+(defun org-ansi-null-context-p (context)
+ "Return non-nil if CONTEXT does not set a face when applied to a region.
+See `org-ansi-context'."
+ (and (zerop (bool-vector-count-population (car context)))
+ (null (cadr context))
+ (null (caddr context))))
+
+(defun org-ansi-clear-context (context)
+ "Destructively clear CONTEXT.
+See `org-ansi-context'."
+ (let ((basic-faces (car context)))
+ ;; From `ansi-color--update-face-vec'
+ (bool-vector-intersection basic-faces #&8"\0" basic-faces)
+ (setcar (cdr context) nil)
+ (setcar (cddr context) nil)))
+
+(defun org-ansi-pack-context (context)
+ "Return an integer representing CONTEXT.
+CONTEXT is of the form of `org-ansi-context' and its information
+is packed into an integer representation so that it can be stored
+as the `org-ansi-context' text property of highlighted regions.
+
+The format is <BASIC-FACES><FG><FG-EMPTY><BG><BG-EMPTY> where
+<BASIC-FACES> are the 8 bits of the `bool-vector' representing
+the switches that can be turned on for ANSI sequences,
+e.g. underline. <FG> (<BG>) are 24 bits for the
+foreground (background) color and <FG-EMPTY> (<BG-EMPTY>) is 1
+bit representing whether or not there is a
+foreground (background) color present for the context."
+ ;; NOTE: The alternative to packing the context into an integer
+ ;; would be storing a copy of the context directly as the
+ ;; `org-ansi-context' property of the highlighted regions. There
+ ;; would be a large memory overhead though with that approach since
+ ;; every highlighted region would have a context list as the
+ ;; property and there can be many highlighted regions, for example
+ ;; the ANSI codes in Python backtraces.
+ (pcase-let ((`(,bf ,fg ,bg) context))
+ (logior
+ (ash (cl-loop
+ with x = 0
+ for i from 0 to (1- (length bf))
+ if (aref bf i) do (setq x (+ x (ash 1 i)))
+ finally return x)
+ (+ 25 25))
+ (if fg
+ (logior (ash fg (+ 25 1))
+ (ash 1 25))
+ 0)
+ (if bg
+ (logior (ash bg 1) 1)
+ 0))))
+
+(defun org-ansi-unpack-to-context (int)
+ "Return INT in an unpacked form assuming it is a packed `org-ansi-context'.
+Return a list in the same format as `org-ansi-context' which see.
+See also `org-ansi-pack-context'."
+ (list
+ (apply #'bool-vector
+ (cl-loop
+ with mask = (ash 1 (+ 25 25))
+ repeat 8
+ collect (not (zerop (logand int mask)))
+ and do (cl-callf ash mask 1)))
+ (unless (zerop (logand (ash 1 25) int))
+ (logand #xffffff (ash int (- (+ 25 1)))))
+ (unless (zerop (logand 1 int))
+ (logand #xffffff (ash int -1)))))
+
+(defun org-ansi-context-contained-p (a b)
+ "Return non-nil if some of the effect of A is contained in B.
+A and B are assumed to be integer representations of an
+`org-ansi-context', see `org-ansi-pack-context'."
+ (let ((get
+ (lambda (color int)
+ (when (eq color 'fg)
+ (cl-callf ash int -25))
+ (unless (zerop (logand 1 int))
+ (logand #xffffff (ash int -1))))))
+ (or (let ((bf-mask (ash #xff (+ 25 25))))
+ (not (zerop (logand (logand a bf-mask)
+ (logand b bf-mask)))))
+ (when-let* ((fg-a (funcall get 'fg a)))
+ (eq fg-a (funcall get 'fg b)))
+ (when-let* ((bg-a (funcall get 'bg a)))
+ (eq bg-a (funcall get 'bg b))))))
+
+(defun org-ansi-previous-context (pos limit)
+ "Return the `org-ansi-context' property before POS.
+Search before POS down to LIMIT for the first non-nil
+`org-ansi-context' property and return its value. If there is no
+non-nil property after LIMIT, return nil."
+ (let ((pos (save-excursion
+ (goto-char pos)
+ ;; Return a position before `point' containing a
+ ;; non-nil `org-ansi-context' property.
+ (let ((pos (point)) context)
+ (while (and (< limit pos)
+ (null context))
+ (setq context (get-text-property
+ (max (1- pos) (point-min)) 'org-ansi-context)
+ pos (previous-single-property-change
+ pos 'org-ansi-context nil limit)))
+ (when context
+ pos)))))
+ (when pos
+ (get-text-property pos 'org-ansi-context))))
+
+(defun org-ansi-point-context ()
+ "Return the ANSI context associated with `point'.
+If no context is associated with `point' return nil."
+ (when-let ((packed-context
+ (let ((el (org-element-at-point)))
+ ;; A region AB where there is a context at the end of
+ ;; A, but no context anywhere in B will result in that
+ ;; ending context of A being picked up here by
+ ;; `org-ansi-previous-context' since that function
+ ;; finds the first non-null context between POS and
+ ;; LIMIT. Since B has no context and A ends in a
+ ;; context, it must be that A ends in an effectively
+ ;; null context (i.e. no foreground or background)
+ ;; which is just the implicit context on B so
+ ;; everything works out OK.
+ (or (org-ansi-previous-context (point) (org-element-begin el))
+ (when-let ((parent (org-ansi-result-element el)))
+ (org-ansi-previous-context
+ (org-element-begin el)
+ (org-element-contents-begin parent)))))))
+ (org-ansi-unpack-to-context packed-context)))
+
+(defvar org-element-greater-elements)
+
+(defun org-ansi-result-element (el)
+ "Return non-nil if ANSI sequences in EL can span multiple elements.
+They can if EL is contained in a greater element with a RESULTS
+affiliated keyword. Or if EL is such a greater element.
+
+Specifically returns that greater element or nil."
+ (if (and (org-element-property :results el)
+ (memq (org-element-type el) org-ansi-highlightable-elements)
+ (memq (org-element-type el) org-element-greater-elements))
+ el
+ (let ((parent el))
+ (while (and parent
+ (not (eq (org-element-type parent) 'section))
+ (not (org-element-property :results parent)))
+ (setq parent (org-element-parent parent)))
+ (when (and parent (not (eq parent el))
+ (org-element-property :results parent)
+ (memq (org-element-type parent)
+ org-ansi-highlightable-elements))
+ parent))))
+
+(defun org-ansi-highlightable-element-p (el)
+ "Return non-nil if EL can have ANSI sequences highlighted in it.
+See `org-ansi-highlightable-elements'."
+ (or (org-ansi-result-element el)
+ (memq (org-element-type el) org-ansi-highlightable-elements)))
+
+(defun org-ansi-extent-of-context ()
+ "Return the end of the influence of the ANSI context at `point'.
+Return nil if `point' has no ANSI context."
+ (when-let ((context (get-text-property (point) 'org-ansi-context)))
+ (let* ((el (org-element-at-point))
+ (pos (next-single-property-change (point) 'org-ansi-context))
+ (end (cadr (org-ansi-widened-element-and-end el))))
+ (while (and (< pos end)
+ (let ((other (get-text-property pos 'org-ansi-context)))
+ (or (null other)
+ (eq context other)
+ (org-ansi-context-contained-p context other))))
+ (setq pos (next-single-property-change pos 'org-ansi-context nil end)))
+ (unless (get-text-property pos 'org-ansi-context)
+ (setq pos (previous-single-property-change pos 'org-ansi-context)))
+ pos)))
+
+(defun org-ansi-widened-element-and-end (el)
+ "Return the `org-ansi-result-element' of EL and its processing end.
+Specifically return a list (ELEM END) where ELEM is either the
+`org-ansi-result-element' of EL or EL itself if that is nil and
+END is the processing limit of ELEM."
+ (if-let ((parent (org-ansi-result-element el)))
+ (list parent (org-element-contents-end parent))
+ (list el (pcase (org-element-type el)
+ ((or `headline `inlinetask)
+ (org-element-contents-begin el))
+ (_
+ (or (org-element-contents-end el)
+ (org-element-end el)))))))
+
+;; What will be set as the `ansi-color-context-region' below.
+(defvar org-ansi-ansi-color-context (list nil (make-marker)))
+
+(defun org-ansi-apply-on-region (beg end &optional face-function seq-function)
+ "Apply ANSI sequences between (BEG END), maintain Org specific state.
+Calls `ansi-color-apply-on-region' on the region between BEG and
+END using FACE-FUNCTION as the `ansi-color-apply-face-function'
+which defaults to a function prepends the face and adds an
+`org-ansi-context' property to the highlighted regions.
+
+SEQ-FUNCTION is a function to apply to the ANSI sequences found
+in the region. It is called with the bounds of the sequence as
+arguments. It defaults to doing nothing on the sequences."
+ (setcar org-ansi-ansi-color-context org-ansi-context)
+ (move-marker (cadr org-ansi-ansi-color-context) beg)
+ (let ((ansi-color-context-region org-ansi-ansi-color-context)
+ (ansi-color-apply-face-function
+ (or face-function
+ (lambda (beg end face)
+ (when face
+ (font-lock-prepend-text-property beg end 'face face))
+ (add-text-properties
+ beg end (list 'org-ansi-context
+ (org-ansi-pack-context org-ansi-context)))))))
+ (ansi-color-apply-on-region beg end t))
+ (goto-char beg)
+ (while (re-search-forward ansi-color-control-seq-regexp end 'noerror)
+ (let ((beg (match-beginning 0))
+ (end (point)))
+ (when seq-function
+ (funcall seq-function beg end))
+ (dolist (ov (overlays-at beg))
+ (when (and (= beg (overlay-start ov))
+ (= end (overlay-end ov))
+ (overlay-get ov 'invisible))
+ ;; Assume this is the overlay added by
+ ;; `ansi-color-apply-on-region'.
+ (delete-overlay ov))))))
+
+(defvar font-lock-beg)
+(defvar font-lock-end)
+
+(defun org-ansi-extend-region ()
+ "A `font-lock-extend-region-functions' function specific for ANSI sequences.
+This handles two cases, extending due to deletions or
+modifications of ANSI sequences between font-lock cycles and
+extending due to splits of elements into multiple other elements
+between font-lock cycles. The latter handling takes care of
+cases where the bounds of the effects of sequences can be altered
+due to the splitting of elements between font-lock cycles,
+e.g. one paragraph into two."
+ (let ((old-end font-lock-end)
+ (end font-lock-end)
+ (changed nil))
+ (save-excursion
+ ;; Extend due to deletions or modifications of sequences.
+ (goto-char font-lock-beg)
+ (while (< (point) end)
+ (let ((context (get-text-property (point) 'org-ansi-context))
+ (seq-state (get-text-property (point) 'org-ansi)))
+ (if (and context seq-state)
+ (if (and (looking-at ansi-color-control-seq-regexp)
+ (eq (intern (buffer-substring-no-properties
+ (match-beginning 0) (match-end 0)))
+ seq-state))
+ (goto-char (next-single-property-change
+ (point) 'org-ansi-context nil end))
+ ;; Either a sequence was deleted or a sequence was
+ ;; replaced with some other sequence. Extend the
+ ;; region to include the extent of the changed
+ ;; sequence.
+ (let ((ctx-end (org-ansi-extent-of-context)))
+ (setq end (max end ctx-end))
+ (goto-char ctx-end)))
+ (goto-char (next-single-property-change
+ (point) 'org-ansi-context nil end)))))
+ (unless (eq old-end end)
+ (goto-char end)
+ (unless (eq (point) (line-beginning-position))
+ (forward-line))
+ (setq font-lock-end (point)
+ changed t))
+ ;; Extend due to splits of elements into multiple other
+ ;; elements.
+ (goto-char font-lock-end)
+ (skip-chars-forward " \r\n\t")
+ (let* ((el (org-element-at-point))
+ ;; FIXME Consider elements like plain-list and table, we
+ ;; don't want to end up fontifying the whole plain-list
+ ;; or table if the highlighting can be determined to only
+ ;; be up to some point before the end, e.g. within a
+ ;; paragraph or table row.
+ (end (pcase (org-element-type el)
+ ((or `headline `inlinetask)
+ (org-element-contents-begin el))
+ (_
+ (org-element-end el)))))
+ ;; Move to the first highlight within the element if not
+ ;; already at one.
+ (unless (get-text-property (point) 'org-ansi-context)
+ (let ((next (next-single-property-change
+ (point) 'org-ansi-context nil end)))
+ (unless (eq next end)
+ (goto-char next))))
+ (when (get-text-property (point) 'org-ansi-context)
+ (if (get-text-property (point) 'org-ansi)
+ (let ((seq-context
+ (progn
+ (org-ansi-clear-context org-ansi-context)
+ ;; Purely for the side effect of
+ ;; setting `org-ansi-context'
+ (org-ansi-apply-on-region
+ (point)
+ (next-single-property-change (point) 'org-ansi)
+ #'ignore)
+ (org-ansi-pack-context org-ansi-context)))
+ (context (get-text-property (point) 'org-ansi-context)))
+ (unless (eq seq-context context)
+ (setq font-lock-end (org-ansi-extent-of-context)
+ changed t)))
+ ;; Include the whole element for lack of a better way of
+ ;; determining when to stop. See FIXME above. Could just
+ ;; look for the next sequence in this element...
+ (setq font-lock-end end
+ changed t)))))
+ changed))
+
+(defun org-ansi-process-region (beg end)
+ "Process ANSI sequences in the region (BEG END).
+Use and update the value of `org-ansi-context' during the
+processing."
+ (let* ((highlight-beg beg)
+ (set-seq-properties
+ (lambda (beg end)
+ (let ((seq (intern (buffer-substring-no-properties beg end))))
+ (remove-text-properties highlight-beg beg '(org-ansi t))
+ (setq highlight-beg end)
+ (add-text-properties
+ beg end (list 'invisible 'org-ansi
+ 'rear-nonsticky '(org-ansi)
+ 'org-ansi seq))
+ (put-text-property beg end 'org-ansi-context
+ (or (get-text-property end 'org-ansi-context)
+ ;; Handle edge case that a sequence
+ ;; occurs at the end of the region
+ ;; being processed.
+ (org-ansi-pack-context org-ansi-context)))))))
+ (org-ansi-apply-on-region beg end nil set-seq-properties)
+ (remove-text-properties highlight-beg end '(org-ansi t))))
+
+(defun org-ansi-process-object (obj)
+ "Highlight the ANSI sequences contained in OBJ."
+ (org-ansi-process-region
+ (point)
+ (or (org-element-contents-end obj)
+ (- (org-element-end obj)
+ (org-element-post-blank obj)
+ 1)))
+ (goto-char (org-element-end obj)))
+
+(defun org-ansi-process-lines (beg end)
+ "Highlight the ANSI sequences of the lines between BEG and END.
+Exclude whitespace at the beginning of the lines."
+ (goto-char beg)
+ (while (< (point) end)
+ (org-ansi-process-region (point) (min end (line-end-position)))
+ (forward-line)
+ (skip-chars-forward " \t"))
+ (goto-char end))
+
+(defvar org-element-all-objects)
+
+(defun org-ansi-process-lines-consider-objects (beg end)
+ "Highlight the ANSI sequences of the lines between BEG and END.
+Consider objects when highlighting."
+ (goto-char beg)
+ (while (re-search-forward ansi-color-control-seq-regexp end 'noerror)
+ (goto-char (match-beginning 0))
+ (let ((seq-end (match-end 0))
+ (el (org-element-context)))
+ ;; If the context is empty and the current sequence lies in an
+ ;; object, relegate the effect of the sequence to the object.
+ (if (org-ansi-null-context-p org-ansi-context)
+ (let ((type (org-element-type el)))
+ (if (memq type org-element-all-objects)
+ (if (not (memq type org-ansi-highlightable-objects))
+ (goto-char seq-end)
+ (org-ansi-process-object el)
+ (org-ansi-clear-context org-ansi-context)
+ (setq beg (point)))
+ (org-ansi-process-lines beg seq-end)))
+ (org-ansi-process-lines beg seq-end))
+ (setq beg seq-end)))
+ (org-ansi-process-lines beg end))
+
+(defun org-ansi-process-element (el &optional limit)
+ "Process ANSI sequences in EL up to LIMIT.
+EL should be a lesser element or headline. If EL can't be
+processed, move `point' to its end. Otherwise process the
+element, i.e. highlight the ANSI sequences beginning at
+`point' (assumed to be within EL) and ending at LIMIT or the end
+of the element, whichever comes first.
+
+After a call to this function `point' will be at LIMIT or the
+next element that comes after EL."
+ (pcase (org-element-type el)
+ ((or `headline `inlinetask)
+ (org-ansi-process-lines-consider-objects
+ (point) (line-end-position))
+ (goto-char (org-element-contents-begin el)))
+ (`table-row
+ ;; NOTE Limit not used here since a row is a line and it doesn't
+ ;; seem to make sense to process only some of the cells in a row.
+ ;; Limit is usually a line beginning position anyways which is
+ ;; the end of a table row in the first place.
+ (if (eq (org-element-property :type el) 'rule)
+ (goto-char (org-element-end el))
+ (let ((end-1 (1- (org-element-end el))))
+ (while (< (point) end-1)
+ (let ((cell (org-element-context)))
+ (org-ansi-process-region
+ (org-element-contents-begin cell)
+ (org-element-contents-end cell))
+ (goto-char (org-element-end cell))))
+ (forward-char))))
+ ((or `example-block `export-block `src-block)
+ (let ((beg (point))
+ (end (save-excursion
+ (goto-char (org-element-end el))
+ (skip-chars-backward " \t\r\n")
+ (line-beginning-position))))
+ (setq limit (if limit (min end limit)
+ end))
+ (org-ansi-process-lines beg limit)
+ (if (eq limit end)
+ (goto-char (org-element-end el))
+ (goto-char limit))))
+ (`fixed-width
+ (setq limit (if limit (min (org-element-end el) limit)
+ (org-element-end el)))
+ (while (< (point) limit)
+ (when (eq (char-after) ?:)
+ (forward-char)
+ (when (eq (char-after) ?\s)
+ (forward-char)))
+ (org-ansi-process-region (point) (line-end-position))
+ (skip-chars-forward " \n\r\t")))
+ (`paragraph
+ (let ((pend (1- (org-element-contents-end el))) beg end)
+ (setq limit (if limit (min pend limit) pend))
+ ;; Compute the regions of the paragraph excluding inline
+ ;; source blocks or babel calls.
+ (push (point) beg)
+ (while (re-search-forward
+ "\\<\\(src\\|call\\)_[^ \t\n[{]+[{(]" limit t)
+ (let ((el (org-element-context)))
+ (when (memq (org-element-type el)
+ '(inline-src-block inline-babel-call))
+ (push (org-element-begin el) end)
+ (goto-char (min (org-element-end el) limit))
+ (push (point) beg))))
+ (push limit end)
+ (setq beg (nreverse beg)
+ end (nreverse end))
+ (while beg
+ (org-ansi-process-lines-consider-objects (pop beg) (pop end)))
+ (if (eq limit pend)
+ (goto-char (org-element-end el))
+ (goto-char limit))))
+ (_
+ (goto-char (org-element-end el)))))
+
+(defun org-ansi-visit-elements (limit visitor)
+ "Visit highlightable elements between `point' and LIMIT with VISITOR.
+LIMIT is supposed to be a hard limit which VISITOR should not
+visit anything past it.
+
+VISITOR is a function that takes an element and LIMIT as
+arguments. It is called for every highlightable lesser element
+within the visited region. After being called it is expected
+that `point' is moved past the visited element, to the next
+element to potentially process, or to LIMIT, whichever comes
+first."
+ (declare (indent 1))
+ (let ((skip-to-end-p
+ (lambda (el)
+ (or (null (org-element-contents-begin el))
+ (<= (org-element-contents-end el)
+ (point)
+ (org-element-end el))))))
+ (while (< (point) limit)
+ (let* ((el (org-element-at-point))
+ (type (org-element-type el)))
+ (pcase type
+ ;; Greater elements
+ ((or `item `center-block `quote-block `special-block
+ `dynamic-block `drawer `footnote-definition)
+ (if (funcall skip-to-end-p el)
+ (goto-char (org-element-end el))
+ (goto-char (org-element-contents-begin el))
+ (org-ansi-visit-elements
+ (min limit (org-element-contents-end el))
+ visitor)))
+ (`property-drawer
+ (goto-char (org-element-end el)))
+ (`plain-list
+ (if (funcall skip-to-end-p el)
+ (goto-char (org-element-end el))
+ (let ((end (min limit (org-element-end el))))
+ (goto-char (org-element-contents-begin el))
+ (while (< (point) end)
+ ;; Move to within the first item of a list.
+ (forward-char)
+ (let* ((item (org-element-at-point))
+ (cbeg (org-element-contents-begin item)))
+ (when cbeg
+ (goto-char cbeg)
+ (org-ansi-visit-elements
+ (min limit (org-element-contents-end item))
+ visitor))
+ (when (< (point) limit)
+ (goto-char (org-element-end item)))
+ (skip-chars-forward " \t\n\r"))))))
+ (`table
+ (if (funcall skip-to-end-p el)
+ (goto-char (org-element-end el))
+ (goto-char (org-element-contents-begin el))
+ ;; Move to within the table-row of a table to continue
+ ;; processing it.
+ (forward-char)))
+ ((or `headline `inlinetask)
+ (if (funcall skip-to-end-p el)
+ (goto-char (org-element-end el))
+ (if (org-ansi-highlightable-element-p el)
+ (funcall visitor el limit)
+ (goto-char (org-element-contents-begin el)))))
+ ((guard (org-ansi-highlightable-element-p el))
+ (let ((visit t))
+ ;; Move to the beginning of the highlightable region if not already
+ ;; within one.
+ (pcase (org-element-type el)
+ (`table-row
+ (if (eq (org-element-property :type el) 'rule)
+ (progn
+ (setq visit nil)
+ (goto-char (org-element-end el)))
+ (when (< (point) (org-element-contents-begin el))
+ (goto-char (org-element-contents-begin el)))))
+ ((or `example-block `export-block `src-block)
+ (let ((start (save-excursion
+ (goto-char (org-element-post-affiliated el))
+ (line-beginning-position 2))))
+ (when (< (point) start)
+ (goto-char start))))
+ (`fixed-width
+ (when (< (point) (org-element-post-affiliated el))
+ (goto-char (org-element-post-affiliated el))))
+ (`paragraph
+ (when (< (point) (org-element-contents-begin el))
+ (goto-char (org-element-contents-begin el)))))
+ (when visit
+ ;; Move past any whitespace at the beginning of a line if
+ ;; `point' is within that whitespace.
+ (let ((pos (point))
+ (skipped (not (zerop (skip-chars-backward " \t")))))
+ (if (eq (point) (line-beginning-position))
+ (skip-chars-forward " \t")
+ (when skipped
+ (goto-char pos))))
+ (funcall visitor el limit))))
+ (_
+ (goto-char (org-element-end el))))))
+ ;; Move to the next element when `point' is basically at the end
+ ;; of an element.
+ (let ((el (org-element-at-point)))
+ (when (and (org-element-contents-begin el)
+ (<= (org-element-contents-end el)
+ (point)
+ (org-element-end el)))
+ (goto-char (org-element-end el))))))
+
+(defvar org-ansi-mode)
+
+(defun org-fontify-ansi-sequences (limit)
+ "Fontify ANSI sequences."
+ (when (and org-fontify-ansi-sequences org-ansi-mode)
+ (or org-ansi-context
+ (setq org-ansi-context (org-ansi-new-context)))
+ (org-ansi-clear-context org-ansi-context)
+ (let* ((last-el-processed nil)
+ (process
+ (lambda (el limit &optional context)
+ (when-let ((context (or context (org-ansi-point-context))))
+ (setq org-ansi-context context))
+ (pcase-let* ((`(,widened-el ,end) (org-ansi-widened-element-and-end el))
+ ;; Preserve the context when processing a
+ ;; highlightable greater element or when
+ ;; the processing limit falls within an
+ ;; element. In both cases, the context may
+ ;; be needed for post processing.
+ (preserve-context (or (< limit end)
+ (not (eq widened-el el)))))
+ (org-ansi-visit-elements (min end limit)
+ (lambda (el limit)
+ (setq last-el-processed el)
+ (org-ansi-process-element el limit)
+ (unless preserve-context
+ (org-ansi-clear-context org-ansi-context))))))))
+ (skip-chars-forward " \n\r\t")
+ (while (< (point) limit)
+ (let ((context (org-ansi-point-context)))
+ (cond
+ (context
+ ;; A context exists before point in this element so it
+ ;; must have been highlightable, process the element
+ ;; starting with the previous context.
+ (funcall process (org-element-at-point) limit context))
+ (t
+ ;; No previous context at this point, so it's safe to
+ ;; begin processing at the start of the next sequence.
+ ;; There is no context prior to the sequence to consider.
+ (when (re-search-forward ansi-color-control-seq-regexp limit 'noerror)
+ (goto-char (match-beginning 0))
+ (funcall process (org-element-at-point) limit)))))
+ (skip-chars-forward " \n\r\t"))
+ ;; Post processing to highlight to the proper end (past limit)
+ ;; when there is a non-null context remaining and the region
+ ;; after limit does not match with the context.
+ (pcase-let* ((el (org-element-at-point))
+ (`(,widened-el ,end) (org-ansi-widened-element-and-end el)))
+ (when (and (not (org-ansi-null-context-p org-ansi-context))
+ (or
+ ;; A partial processing of the element. `point'
+ ;; is still inside of it.
+ (eq last-el-processed el)
+ ;; Inside a highlightable greater element with a
+ ;; RESULTS affiliated keyword.. Processing ended
+ ;; at the end of an element and thus `point' will
+ ;; be at the beginning of the next element. If
+ ;; that next element is inside the same greater
+ ;; element then the highlighting should continue
+ ;; through to that next element and beyond.
+ (and (not (eq widened-el el))
+ (<= (org-element-contents-begin widened-el) (point)
+ (org-element-contents-end widened-el)))))
+ (let ((visit 'check))
+ (catch 'visit
+ (org-ansi-visit-elements end
+ (lambda (el limit)
+ (when (eq visit 'check)
+ (let ((context (get-text-property
+ (point) 'org-ansi-context)))
+ (when (eq context
+ (org-ansi-pack-context org-ansi-context))
+ ;; Only continue the highlighting past limit
+ ;; when the contexts don't match.
+ (throw 'visit nil)))
+ (setq visit t))
+ (org-ansi-process-element el limit)
+ (when (eq widened-el el)
+ (org-ansi-clear-context org-ansi-context)))))))))))
+
+(defun org-toggle-ansi-display ()
+ "Toggle the visible state of ANSI sequences in the current buffer."
+ (interactive)
+ (setq org-ansi-hide-sequences (not org-ansi-hide-sequences))
+ (if org-ansi-hide-sequences
+ (add-to-invisibility-spec 'org-ansi)
+ (remove-from-invisibility-spec 'org-ansi)))
+
(defun org-activate-footnote-links (limit)
"Add text properties for footnotes."
(let ((fn (org-footnote-next-reference-or-definition limit)))
@@ -5971,6 +6687,7 @@ (defun org-set-font-lock-defaults ()
;; `org-fontify-inline-src-blocks' prepends object boundary
;; faces and overrides native faces.
'(org-fontify-inline-src-blocks)
+ '(org-fontify-ansi-sequences)
;; Citations. When an activate processor is specified, if
;; specified, try loading it beforehand.
(progn
@@ -6159,7 +6876,7 @@ (defun org-unfontify-region (beg end &optional _maybe_loudly)
(remove-text-properties beg end
'(mouse-face t keymap t org-linked-text t
invisible t intangible t
- org-emphasis t))
+ org-emphasis t org-ansi-context t))
(org-fold-core-update-optimisation beg end)
(org-remove-font-lock-display-properties beg end)))
@@ -15950,6 +16667,30 @@ (defun org-agenda-prepare-buffers (files)
(when org-agenda-file-menu-enabled
(org-install-agenda-files-menu))))
+\f
+;;;; ANSI minor mode
+
+(define-minor-mode org-ansi-mode
+ "Toggle the minor `org-ansi-mode'.
+This mode adds support to highlight ANSI sequences in Org mode.
+The sequences are highlighted only if the customization
+`org-fontify-ansi-sequences' is non-nil when the mode is enabled.
+\\{org-ansi-mode-map}"
+ :lighter " OANSI"
+ (if org-ansi-mode
+ (progn
+ (add-hook 'font-lock-extend-region-functions
+ #'org-ansi-extend-region 'append t)
+ (if org-ansi-hide-sequences
+ (add-to-invisibility-spec 'org-ansi)
+ (remove-from-invisibility-spec 'org-ansi)))
+ (remove-hook 'font-lock-extend-region-functions
+ #'org-ansi-extend-region t)
+ (remove-from-invisibility-spec 'org-ansi))
+ (org-restart-font-lock))
+
+(add-hook 'org-mode-hook #'org-ansi-mode)
+
\f
;;;; CDLaTeX minor mode
diff --git a/testing/lisp/test-org.el b/testing/lisp/test-org.el
index 2487c9a..a376d90 100644
--- a/testing/lisp/test-org.el
+++ b/testing/lisp/test-org.el
@@ -28,6 +28,8 @@ (require 'org)
(require 'org-inlinetask)
(require 'org-refile)
(require 'org-agenda)
+(require 'faceup)
+
\f
;;; Helpers
@@ -2253,6 +2255,317 @@ (ert-deftest test-org/clone-with-time-shift ()
(org-test-with-result 'buffer
(org-clone-subtree-with-time-shift 1 "-2h")))))))
+\f
+;;; ANSI sequences
+
+(ert-deftest test-org/ansi-sequence-fontification ()
+ "Test correct behavior of ANSI sequences."
+ (let ((org-fontify-ansi-sequences t))
+ (cl-labels
+ ((faceup
+ (text)
+ (org-test-with-temp-text text
+ (org-ansi-mode)
+ (font-lock-ensure)
+ (let ((fontified (current-buffer)))
+ (with-temp-buffer
+ (faceup-markup-to-buffer (current-buffer) fontified)
+ (buffer-string)))))
+ (test
+ (text text-faceup)
+ ;; Don't spill over sequences to the rest of the terminal
+ ;; when a test fails.
+ (setq text (concat text "\n^[[0m\n")
+ text-faceup (concat text-faceup "\n^[[0m\n"))
+ (should (faceup-test-equal (faceup text) text-faceup))))
+ (cl-macrolet ((face (f &rest args)
+ (let* ((short-name (alist-get f faceup-face-short-alist))
+ (name (or short-name f))
+ (prefix (format (if short-name "%s:" "%S:") name)))
+ (unless short-name
+ (cl-callf2 concat ":" prefix))
+ (cl-callf2 concat "«" prefix)
+ `(concat ,prefix ,@args "»")))
+ (fg (&rest args) `(face (:foreground "green3") ,@args))
+ (bg (&rest args) `(face (:background "green3") ,@args))
+ (fg-bg (&rest args) `(fg (bg ,@args)))
+ (bold (&rest args) `(face bold ,@args))
+ (org (text) `(faceup ,text))
+ (fg-start () "^[[32m")
+ (bg-start () "^[[42m")
+ (clear () "^[[0m"))
+ ;; Objects
+ ;; Sequence's effect remains in object...
+ (test
+ (concat "1 An *obj" (fg-start) "ect*. text after\n")
+ (concat "1 An " (bold "*obj" (fg-start) (fg "ect") "*") ". text after\n"))
+ ;; ...except when there were sequences at the element level previously.
+ (test
+ (concat "2 " (fg-start) "text *obj" (bg-start) "ect*. text after\n")
+ (concat "2 " (fg-start) (fg "text ")
+ (bold (fg "*obj") (bg-start) (fg-bg "ect*"))
+ (fg-bg ". text after") "\n"))
+ ;; Sequence in object before sequence at element level.
+ (test
+ (concat
+ "3 *obj" (fg-start) "ect*. text "
+ (bg-start) "after\n")
+ (concat
+ "3 " (bold "*obj" (fg-start) (fg "ect") "*") ". text "
+ (bg-start) (bg "after") "\n"))
+ ;; Clearing the ANSI context in a paragraph, resets things so
+ ;; that sequences appearing in objects later in the paragraph
+ ;; have their effects localized to the objects.
+ (test
+ (concat
+ "4 *obj" (fg-start) "ect* " (fg-start) " text"
+ (clear) " text *obj" (bg-start) "ect* more text\n")
+ (concat
+ "4 " (bold "*obj" (fg-start) (fg "ect") "*") " " (fg-start) (fg " text")
+ (clear) " text " (bold "*obj" (bg-start) (bg "ect") "*") " more text\n"))
+ ;; Tables
+ (test
+ (concat
+ "#+RESULTS:\n"
+ "| " (fg-start) "10a | b |\n"
+ "| c | d |\n")
+ (concat
+ (org "#+RESULTS:\n")
+ (face org-table "| " (fg-start) (fg "10a") " | " (fg "b") " |") (face org-table-row "\n")
+ (face org-table "| " (fg "c") " | " (fg "d") " |") (face org-table-row "\n")))
+ (test
+ (concat
+ "| " (fg-start) "5a | b |\n"
+ "| cell | d |\n")
+ (concat
+ (face org-table "| " (fg-start) (fg "5a")" | " (fg "b") " |") (face org-table-row "\n")
+ (face org-table "| cell" " | d |") (face org-table-row "\n")))
+ ;; Paragraphs
+ (test
+ (concat
+ (fg-start) "6 paragraph1\ntext\n"
+ "\nparagraph2\n\n"
+ (fg-start) "text src_python{return 1 + 1} "
+ (bg-start) "more text\n")
+ (concat
+ (fg-start) (fg "6 paragraph1") "\n"
+ (fg "text") "\n"
+ "\nparagraph2\n\n"
+ ;; Effect of sequences skips inline source blocks.
+ (fg-start) (fg "text ") (org "src_python{return 1 + 1} ")
+ (bg-start) (fg (bg "more text")) "\n"))
+ ;; Don't fontify whitespace
+ ;; Fixed width
+ (test
+ (concat
+ "#+RESULTS:\n"
+ ": 4 one " (fg-start) "two\n"
+ ": three\n")
+ (concat
+ (org "#+RESULTS:\n")
+ (face org-code
+ ": 4 one " (fg-start) (fg "two") "\n"
+ ": " (fg "three") "\n")))
+ ;; Blocks
+ (test
+ (concat
+ "#+begin_example\n"
+ "5 li " (fg-start) "ne 1\n"
+ "line 2\n"
+ "line 3\n"
+ "#+end_example\n"
+ "\ntext after\n")
+ (concat
+ (face org-block-begin-line "#+begin_example\n")
+ (face org-block
+ "5 li " (fg-start) (fg "ne 1") "\n"
+ (fg "line 2") "\n"
+ (fg "line 3") "\n")
+ (face org-block-end-line "#+end_example\n")
+ "\ntext after\n"))
+ ;; Avoid processing some elements according to
+ ;; `org-ansi-highlightable-elements' or
+ ;; `org-ansi-highlightable-objects'.
+ (let ((org-ansi-highlightable-objects
+ (delete 'verbatim org-ansi-highlightable-objects))
+ (org-ansi-highlightable-elements
+ (delete 'src-block org-ansi-highlightable-elements)))
+ (test
+ (concat
+ "6 =verb" (fg-start) "atim=\n\n"
+ "#+begin_src python\n"
+ "return \"str " (fg-start) "ing\"\n"
+ "#+end_src\n")
+ (org
+ (concat
+ "6 =verb" (fg-start) "atim=\n\n"
+ "#+begin_src python\n"
+ "return \"str " (fg-start) "ing\"\n"
+ "#+end_src\n"))))
+ ;; Headlines
+ (test
+ (concat
+ "* 7 Head" (fg-start) "line 1\n"
+ "\ntext after\n")
+ (concat
+ (face org-level-1 "* 7 Head" (fg-start) (fg "line 1")) "\n"
+ "\ntext after\n"))
+ ;; Sequences span the whole list with a RESULTS affiliated
+ ;; keyword.
+ (test
+ (concat
+ "- " (fg-start) "one\n"
+ " - two\n"
+ "- three\n\n"
+ "#+RESULTS:\n"
+ "- " (fg-start) "one\n"
+ " - two\n"
+ "- three\n")
+ (concat
+ "- " (fg-start) (fg "one") "\n"
+ " - two\n"
+ "- three\n\n"
+ (org "#+RESULTS:\n")
+ "- " (fg-start) (fg "one") "\n"
+ " - " (fg "two") "\n"
+ "- " (fg "three") "\n"))
+ (test
+ (concat
+ "#+RESULTS:\n"
+ "| " (fg-start) "b | c |\n"
+ "|---+---|\n"
+ "| a | b |\n\n"
+ "paragraph1\n\n"
+ "-----\n\n"
+ "paragraph2\n")
+ (concat
+ (org "#+RESULTS:\n")
+ (face org-table "| " (fg-start) (fg "b") " | " (fg "c") " |") (face org-table-row "\n")
+ (face org-table "|---+---|") (face org-table-row "\n")
+ (face org-table "| " (fg "a") " | " (fg "b") " |") (face org-table-row "\n")
+ "\nparagraph1\n\n"
+ "-----\n\n"
+ "paragraph2\n"))
+ (test
+ (concat
+ "#+RESULTS:\n"
+ ":drawer:\n"
+ (fg-start) "paragraph\n\n"
+ "#+begin_center\n"
+ "- item1\n"
+ "- item2\n"
+ " - item3\n"
+ "#+end_center\n\n"
+ "paragraph2\n"
+ ":end:\n")
+ (concat
+ (org "#+RESULTS:\n")
+ (org ":drawer:\n")
+ (fg-start) (fg "paragraph") "\n\n"
+ (face org-block-begin-line "#+begin_center\n")
+ "- " (fg "item1") "\n"
+ "- " (fg "item2") "\n"
+ " - " (fg "item3") "\n"
+ (face org-block-end-line "#+end_center\n") "\n"
+ (fg "paragraph2") "\n"
+ (org ":end:\n")))
+ ;; Highlighting context doesn't spill over to elements when it
+ ;; shouldn't.
+ (test
+ (concat
+ "#+BEGIN: dblock\n"
+ "- Item 1\n"
+ "- Item 2\n"
+ "- " (fg-start) "Item 3\n"
+ "#+END:\n\n"
+ "[fn:1] Footnote " (bg-start) "definition\n")
+ (concat
+ (face org-meta-line "#+BEGIN: dblock") "\n"
+ "- Item 1\n"
+ "- Item 2\n"
+ "- " (fg-start) (fg "Item 3") "\n"
+ (face org-meta-line "#+END:") "\n\n"
+ (face org-footnote "[fn:1]") " Footnote " (bg-start) (bg "definition") "\n"))))))
+
+(ert-deftest test-org/ansi-sequence-editing ()
+ (cl-labels ((test (text-faceup)
+ (let ((fontified (current-buffer)))
+ (with-temp-buffer
+ (faceup-markup-to-buffer (current-buffer) fontified)
+ (should (faceup-test-equal (buffer-string) text-faceup)))))
+ (test-lines (n text-faceup &optional no-ensure)
+ (unless no-ensure
+ (font-lock-ensure (line-beginning-position) (1+ (line-end-position n))))
+ (save-restriction
+ (narrow-to-region (line-beginning-position) (1+ (line-end-position n)))
+ (test text-faceup))))
+ (cl-macrolet ((face (f &rest args) `(concat "«" ,(format ":%S:" f) ,@args "»"))
+ (fg (&rest args) `(face (:foreground "green3") ,@args))
+ (fg-start () "^[[32m")
+ (clear () "^[[0m"))
+ ;; fixed-width regions and font-lock-multiline
+ (org-test-with-temp-text
+ (concat "\
+: " (fg-start) "line1
+: line2
+<point>")
+ (org-ansi-mode)
+ (font-lock-ensure)
+ (insert ": line3\n")
+ (forward-line -1)
+ ;; Sequence effects spill over to newly inserted fixed-width line.
+ (test-lines 1 (face org-code ": " (fg "line3") "\n"))
+ (forward-line -1)
+ (goto-char (line-end-position))
+ (insert "text")
+ ;; Editing a line that is affected by some previous line's
+ ;; sequence maintains the effect of that sequence on the
+ ;; line.
+ (test-lines 2 (face org-code
+ ": " (fg "line2text") "\n"
+ ": " (fg "line3") "\n")))
+ ;; Test that the highlighting spans all nested elements inside
+ ;; an element with a RESULTS keyword and the highlighting
+ ;; remains after edits to any of the elements.
+ (org-test-with-temp-text
+ (concat "#+RESULTS:\n"
+ ":drawer:\n"
+ (fg-start) "paragraph\n\n"
+ "#+begin_center\n"
+ "- item1\n"
+ "- item2\n"
+ " - item3\n"
+ "#+end_center\n\n"
+ "paragraph2<point>\n"
+ ":end:\n")
+ (org-ansi-mode)
+ (font-lock-ensure)
+ (insert "more text")
+ (test-lines 1 (concat (fg "paragraph2more text") "\n"))
+ (re-search-backward "item3")
+ (forward-char)
+ (insert "x")
+ (test-lines 1 (concat " - " (fg "ixtem3") "\n")))
+ ;; Joining paragraphs takes into account highlighting.
+ (org-test-with-temp-text
+ (concat (fg-start) "paragraph1\n\n<point>paragraph2\n")
+ (org-ansi-mode)
+ (font-lock-ensure)
+ (test-lines 1 "paragraph2\n")
+ (delete-char -1)
+ (test-lines 1 (concat (fg "paragraph2") "\n")))
+ ;; Splits in a highlighted region remove highlighting from the
+ ;; region split.
+ (org-test-with-temp-text
+ (concat (fg-start) "line1\nline2\n<point>line3\nline4\n")
+ (org-ansi-mode)
+ (font-lock-ensure)
+ (insert "\n")
+ ;; Test `org-ansi-extend-region' by limiting the region
+ ;; font-locked so it can be extended.
+ (font-lock-ensure (point) (1+ (line-end-position)))
+ (test-lines 2 "line3\nline4\n" t)))))
+
\f
;;; Fixed-Width Areas
--
2.41.0
[-- Attachment #3: Type: text/plain, Size: 15 bytes --]
--
Nathaniel
next prev parent reply other threads:[~2024-12-01 8:36 UTC|newest]
Thread overview: 33+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-04-05 12:03 [PATCH] ANSI color on example blocks and fixed width elements Nathaniel Nicandro
2023-04-05 13:43 ` Ihor Radchenko
2023-04-13 20:18 ` [PATCH] Highlight ANSI sequences in the whole buffer (was [PATCH] ANSI color on example blocks and fixed width elements) Nathaniel Nicandro
2023-04-14 8:49 ` Ihor Radchenko
2023-04-25 20:33 ` Nathaniel Nicandro
2023-05-10 10:27 ` Ihor Radchenko
2023-05-15 0:18 ` Nathaniel Nicandro
2023-05-18 19:45 ` Ihor Radchenko
2023-05-23 0:55 ` Nathaniel Nicandro
2023-08-08 11:02 ` Ihor Radchenko
2023-11-08 9:56 ` Ihor Radchenko
2023-11-08 15:35 ` Nathaniel Nicandro
2023-11-10 10:25 ` Ihor Radchenko
2023-11-17 21:18 ` Nathaniel Nicandro
2023-12-14 14:34 ` Ihor Radchenko
2023-12-24 12:49 ` Nathaniel Nicandro
2024-01-17 0:02 ` Nathaniel Nicandro
2024-01-17 12:36 ` Ihor Radchenko
2024-03-26 14:02 ` Nathaniel Nicandro
2024-03-28 8:52 ` Ihor Radchenko
2024-06-29 10:42 ` Ihor Radchenko
2024-07-01 18:39 ` Nathaniel Nicandro
2024-07-06 13:28 ` Ihor Radchenko
2024-07-16 20:53 ` Nathaniel Nicandro
2024-07-17 22:50 ` Nathaniel Nicandro
2024-07-20 17:57 ` Ihor Radchenko
2024-11-17 23:17 ` Nathaniel Nicandro
2024-11-23 16:21 ` Ihor Radchenko
2024-12-01 8:01 ` Nathaniel Nicandro [this message]
2024-12-16 17:27 ` Ihor Radchenko
2023-12-14 14:37 ` Ihor Radchenko
2023-12-15 12:50 ` Matt
2023-12-25 2:20 ` Nathaniel Nicandro
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
List information: https://www.orgmode.org/
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=87plmbygp4.fsf@gmail.com \
--to=nathanielnicandro@gmail.com \
--cc=emacs-orgmode@gnu.org \
--cc=yantar92@posteo.net \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this public inbox
https://git.savannah.gnu.org/cgit/emacs/org-mode.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).