From: Richard Lawrence <rwl@recursewithless.net>
To: 74994@debbugs.gnu.org
Subject: bug#74994: [PATCH 2/2] New major mode icalendar-mode
Date: Fri, 20 Dec 2024 20:53:49 +0100 [thread overview]
Message-ID: <87y10aku5u.fsf@recursewithless.net> (raw)
In-Reply-To: <87bjx6mrjp.fsf@recursewithless.net>
[-- Attachment #1: Type: text/plain, Size: 350 bytes --]
Tags: patch
Here's a second patch which uses the parser provided by the last
patch to implement a new major mode, icalendar-mode, which for now just
provides syntax highlighting and some basic commands for folding and
unfolding lines.
Again, this is a draft; there's still plenty to be done. I'm looking
forward to your feedback.
Thanks,
Richard
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0002-New-major-mode-icalendar-mode.patch --]
[-- Type: text/patch, Size: 40780 bytes --]
From b0f3ee82d3e936c6e09d2fa44dc1f9ec933ac2b6 Mon Sep 17 00:00:00 2001
From: Richard Lawrence <rwl@recursewithless.net>
Date: Fri, 20 Dec 2024 11:15:42 +0100
Subject: [PATCH 3/3] New major mode icalendar-mode
Import icalendar-mode.el from external repo
Move font lock setup from ical:define-* macros into constants in
icalendar-mode.el
Check that face keywords are non-nil, and require icalendar-mode,
when adding to icalendar-font-lock-keywords in ical:define-* macros
---
lisp/calendar/icalendar-macs.el | 10 +-
lisp/calendar/icalendar-mode.el | 609 ++++++++++++++++++++++++++++++
lisp/calendar/icalendar-parser.el | 75 +---
3 files changed, 614 insertions(+), 80 deletions(-)
create mode 100644 lisp/calendar/icalendar-mode.el
diff --git a/lisp/calendar/icalendar-macs.el b/lisp/calendar/icalendar-macs.el
index 2030efc5e6d..fab78fce866 100644
--- a/lisp/calendar/icalendar-macs.el
+++ b/lisp/calendar/icalendar-macs.el
@@ -379,9 +379,7 @@ ical:define-param
;; Associate the print name with the type symbol for
;; `ical:parse-params' and `ical:print-param':
(when ,param-name
- (push (cons ,param-name (quote ,symbolic-name)) ical:param-types))
- ;; TODO: integrate param-name with eldoc in icalendar-mode
- )))
+ (push (cons ,param-name (quote ,symbolic-name)) ical:param-types)))))
\f
;; Define properties:
@@ -797,10 +795,8 @@ ical:define-component
;; Associate the print name with the type symbol for
;; `icalendar-parse-component', `icalendar-print-component' etc.:
(when ,component-name
- (push (cons ,component-name (quote ,symbolic-name)) ical:component-types))
-
- ;; TODO: integrate component-name with eldoc in icalendar-mode
- )))
+ (push (cons ,component-name (quote ,symbolic-name))
+ ical:component-types)))))
(provide 'icalendar-macs)
;; Local Variables:
diff --git a/lisp/calendar/icalendar-mode.el b/lisp/calendar/icalendar-mode.el
new file mode 100644
index 00000000000..0a0d339c89c
--- /dev/null
+++ b/lisp/calendar/icalendar-mode.el
@@ -0,0 +1,609 @@
+;;; icalendar-mode.el --- Major mode for iCalendar format -*- lexical-binding: t; -*-
+;;;
+
+;; Copyright (C) 2024 Richard Lawrence
+
+;; Author: Richard Lawrence <rwl@recursewithless.net>
+;; Keywords: calendar
+
+;; This file is part of GNU Emacs.
+
+;; This file is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This file is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this file. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This file defines icalendar-mode, a major mode for editing
+;; iCalendar data. It defines a syntax table, faces, hooks, and
+;; commands for the mode and sets up syntax highlighting via
+;; font-lock-mode. Syntax highlighting uses the entries for
+;; font-lock-keywords already gathered in icalendar-parser.el, which
+;; see.
+
+;; When activated, icalendar-mode offers to unfold content lines if
+;; necessary, and switch to a new buffer containing the unfolded data;
+;; see `ical:maybe-switch-to-unfolded-buffer'. This is because the
+;; parsing facilities, and thus syntax highlighting, assume that
+;; content lines have already been unfolded. When a buffer is saved,
+;; icalendar-mode also offers to fold long content if necessary, as
+;; required by RFC5545; see `ical:before-save-checks'.
+
+;;; Code:
+\f
+(require 'icalendar-parser)
+
+;; Faces and font lock:
+(defgroup ical:faces
+ '((ical:property-name custom-face)
+ (ical:property-value custom-face)
+ (ical:parameter-name custom-face)
+ (ical:parameter-value custom-face)
+ (ical:component-name custom-face)
+ (ical:keyword custom-face)
+ (ical:binary-data custom-face)
+ (ical:date-time-types custom-face)
+ (ical:numeric-types custom-face)
+ (ical:recurrence-rule custom-face)
+ (ical:warning custom-face)
+ (ical:ignored custom-face))
+ "Faces for icalendar-mode.") ; TODO: :group
+
+(defface ical:property-name
+ '((default . (:inherit font-lock-keyword-face)))
+ "Face for iCalendar property names")
+
+(defface ical:property-value
+ '((default . (:inherit default)))
+ "Face for iCalendar property values")
+
+(defface ical:parameter-name
+ '((default . (:inherit font-lock-property-name-face)))
+ "Face for iCalendar parameter names")
+
+(defface ical:parameter-value
+ '((default . (:inherit font-lock-property-use-face)))
+ "Face for iCalendar parameter values")
+
+(defface ical:component-name
+ '((default . (:inherit font-lock-constant-face)))
+ "Face for iCalendar component names")
+
+(defface ical:keyword
+ '((default . (:inherit font-lock-keyword-face)))
+ "Face for other iCalendar keywords")
+
+(defface ical:binary-data
+ '((default . (:inherit font-lock-comment-face)))
+ "Face for iCalendar values that represent binary data")
+
+(defface ical:date-time-types
+ '((default . (:inherit font-lock-type-face)))
+ "Face for iCalendar values that represent dates, date-times,
+durations, periods, and UTC offsets")
+
+(defface ical:numeric-types
+ '((default . (:inherit ical:property-value-face)))
+ "Face for iCalendar values that represent integers, floats, and geolocations")
+
+(defface ical:recurrence-rule
+ '((default . (:inherit font-lock-type-face)))
+ "Face for iCalendar recurrence rule values")
+
+(defface ical:uri
+ '((default . (:inherit ical:property-value-face :underline t)))
+ "Face for iCalendar values that are URIs (including URLs and mail addresses)")
+
+(defface ical:warning
+ '((default . (:inherit font-lock-warning-face)))
+ "Face for iCalendar syntax errors")
+
+(defface ical:ignored
+ '((default . (:inherit font-lock-comment-face)))
+ "Face for iCalendar syntax which is parsed but ignored")
+
+;;; Font lock:
+(defconst ical:params-font-lock-keywords
+ '((ical:match-other-param
+ (1 'font-lock-comment-face t t)
+ (2 'font-lock-comment-face t t)
+ (3 'ical:warning t t))
+ (ical:match-value-param
+ (1 'ical:parameter-name t t)
+ (2 'ical:keyword t t)
+ (3 'ical:warning t t))
+ (ical:match-tzid-param
+ (1 'ical:parameter-name t t)
+ (2 'ical:parameter-value t t)
+ (3 'ical:warning t t))
+ (ical:match-sent-by-param
+ (1 'ical:parameter-name t t)
+ (2 'ical:uri t t)
+ (3 'ical:warning t t))
+ (ical:match-rsvp-param
+ (1 'ical:parameter-name t t)
+ (2 'ical:keyword t t)
+ (3 'ical:warning t t))
+ (ical:match-role-param
+ (1 'ical:parameter-name t t)
+ (2 'ical:keyword t t)
+ (3 'ical:warning t t))
+ (ical:match-reltype-param
+ (1 'ical:parameter-name t t)
+ (2 'ical:keyword t t)
+ (3 'ical:warning t t))
+ (ical:match-related-param
+ (1 'ical:parameter-name t t)
+ (2 'ical:keyword t t)
+ (3 'ical:warning t t))
+ (ical:match-range-param
+ (1 'ical:parameter-name t t)
+ (2 'ical:keyword t t)
+ (3 'ical:warning t t))
+ (ical:match-partstat-param
+ (1 'ical:parameter-name t t)
+ (2 'ical:keyword t t)
+ (3 'ical:warning t t))
+ (ical:match-member-param
+ (1 'ical:parameter-name t t)
+ (2 'ical:uri t t)
+ (3 'ical:warning t t))
+ (ical:match-language-param
+ (1 'ical:parameter-name t t)
+ (2 'ical:parameter-value t t)
+ (3 'ical:warning t t))
+ (ical:match-fbtype-param
+ (1 'ical:parameter-name t t)
+ (2 'ical:keyword t t)
+ (3 'ical:warning t t))
+ (ical:match-fmttype-param
+ (1 'ical:parameter-name t t)
+ (2 'ical:parameter-value t t)
+ (3 'ical:warning t t))
+ (ical:match-encoding-param
+ (1 'ical:parameter-name t t)
+ (2 'ical:keyword t t)
+ (3 'ical:warning t t))
+ (ical:match-dir-param
+ (1 'ical:parameter-name t t)
+ (2 'ical:uri t t)
+ (3 'ical:warning t t))
+ (ical:match-delegated-to-param
+ (1 'ical:parameter-name t t)
+ (2 'ical:uri t t)
+ (3 'ical:warning t t))
+ (ical:match-delegated-from-param
+ (1 'ical:parameter-name t t)
+ (2 'ical:uri t t)
+ (3 'ical:warning t t))
+ (ical:match-cutype-param
+ (1 'ical:parameter-name t t)
+ (2 'ical:keyword t t)
+ (3 'ical:warning t t))
+ (ical:match-cn-param
+ (1 'ical:parameter-name t t)
+ (2 'ical:parameter-value t t)
+ (3 'ical:warning t t))
+ (ical:match-altrep-param
+ (1 'ical:parameter-name t t)
+ (2 'ical:uri t t)
+ (3 'ical:warning t t)))
+ "Entries for iCalendar property parameters in `font-lock-keywords'.")
+
+(defconst ical:properties-font-lock-keywords
+ '((ical:match-request-status-property
+ (1 'ical:property-name t t)
+ (2 'ical:property-value t t)
+ (3 'ical:warning t t))
+ (ical:match-other-property
+ (1 'ical:property-name t t)
+ (2 'ical:property-value t t)
+ (3 'ical:warning t t))
+ (ical:match-sequence-property
+ (1 'ical:property-name t t)
+ (2 'ical:numeric-types t t)
+ (3 'ical:warning t t))
+ (ical:match-last-modified-property
+ (1 'ical:property-name t t)
+ (2 'ical:date-time-types t t)
+ (3 'ical:warning t t))
+ (ical:match-dtstamp-property
+ (1 'ical:property-name t t)
+ (2 'ical:date-time-types t t)
+ (3 'ical:warning t t))
+ (ical:match-created-property
+ (1 'ical:property-name t t)
+ (2 'ical:date-time-types t t)
+ (3 'ical:warning t t))
+ (ical:match-trigger-property
+ (1 'ical:property-name t t)
+ (2 'ical:date-time-types t t)
+ (3 'ical:warning t t))
+ (ical:match-repeat-property
+ (1 'ical:property-name t t)
+ (2 'ical:numeric-types t t)
+ (3 'ical:warning t t))
+ (ical:match-action-property
+ (1 'ical:property-name t t)
+ (2 'ical:keyword t t)
+ (3 'ical:warning t t))
+ (ical:match-rrule-property
+ (1 'ical:property-name t t)
+ (2 'ical:recurrence-rule t t)
+ (3 'ical:warning t t))
+ (ical:match-rdate-property
+ (1 'ical:property-name t t)
+ (2 'ical:date-time-types t t)
+ (3 'ical:warning t t))
+ (ical:match-exdate-property
+ (1 'ical:property-name t t)
+ (2 'ical:date-time-types t t)
+ (3 'ical:warning t t))
+ (ical:match-uid-property
+ (1 'ical:property-name t t)
+ (2 'ical:property-value t t)
+ (3 'ical:warning t t))
+ (ical:match-url-property
+ (1 'ical:property-name t t)
+ (2 'ical:uri t t)
+ (3 'ical:warning t t))
+ (ical:match-related-to-property
+ (1 'ical:property-name t t)
+ (2 'ical:property-value t t)
+ (3 'ical:warning t t))
+ (ical:match-recurrence-id-property
+ (1 'ical:property-name t t)
+ (2 'ical:date-time-types t t)
+ (3 'ical:warning t t))
+ (ical:match-organizer-property
+ (1 'ical:property-name t t)
+ (2 'ical:uri t t)
+ (3 'ical:warning t t))
+ (ical:match-contact-property
+ (1 'ical:property-name t t)
+ (2 'ical:property-value t t)
+ (3 'ical:warning t t))
+ (ical:match-attendee-property
+ (1 'ical:property-name t t)
+ (2 'ical:uri t t)
+ (3 'ical:warning t t))
+ (ical:match-tzurl-property
+ (1 'ical:property-name t t)
+ (2 'ical:uri t t)
+ (3 'ical:warning t t))
+ (ical:match-tzoffsetto-property
+ (1 'ical:property-name t t)
+ (2 'ical:date-time-types t t)
+ (3 'ical:warning t t))
+ (ical:match-tzoffsetfrom-property
+ (1 'ical:property-name t t)
+ (2 'ical:date-time-types t t)
+ (3 'ical:warning t t))
+ (ical:match-tzname-property
+ (1 'ical:property-name t t)
+ (2 'ical:property-value t t)
+ (3 'ical:warning t t))
+ (ical:match-tzid-property
+ (1 'ical:property-name t t)
+ (2 'ical:property-value t t)
+ (3 'ical:warning t t))
+ (ical:match-transp-property
+ (1 'ical:property-name t t)
+ (2 'ical:keyword t t)
+ (3 'ical:warning t t))
+ (ical:match-freebusy-property
+ (1 'ical:property-name t t)
+ (2 'ical:date-time-types t t)
+ (3 'ical:warning t t))
+ (ical:match-duration-property
+ (1 'ical:property-name t t)
+ (2 'ical:date-time-types t t)
+ (3 'ical:warning t t))
+ (ical:match-dtstart-property
+ (1 'ical:property-name t t)
+ (2 'ical:date-time-types t t)
+ (3 'ical:warning t t))
+ (ical:match-due-property
+ (1 'ical:property-name t t)
+ (2 'ical:date-time-types t t)
+ (3 'ical:warning t t))
+ (ical:match-dtend-property
+ (1 'ical:property-name t t)
+ (2 'ical:date-time-types t t)
+ (3 'ical:warning t t))
+ (ical:match-completed-property
+ (1 'ical:property-name t t)
+ (2 'ical:date-time-types t t)
+ (3 'ical:warning t t))
+ (ical:match-summary-property
+ (1 'ical:property-name t t)
+ (2 'ical:property-value t t)
+ (3 'ical:warning t t))
+ (ical:match-status-property
+ (1 'ical:property-name t t)
+ (2 'ical:keyword t t)
+ (3 'ical:warning t t))
+ (ical:match-resources-property
+ (1 'ical:property-name t t)
+ (2 'ical:property-value t t)
+ (3 'ical:warning t t))
+ (ical:match-priority-property
+ (1 'ical:property-name t t)
+ (2 'ical:numeric-types t t)
+ (3 'ical:warning t t))
+ (ical:match-percent-complete-property
+ (1 'ical:property-name t t)
+ (2 'ical:numeric-types t t)
+ (3 'ical:warning t t))
+ (ical:match-location-property
+ (1 'ical:property-name t t)
+ (2 'ical:property-value t t)
+ (3 'ical:warning t t))
+ (ical:match-geo-property
+ (1 'ical:property-name t t)
+ (2 'ical:numeric-types t t)
+ (3 'ical:warning t t))
+ (ical:match-description-property
+ (1 'ical:property-name t t)
+ (2 'ical:property-value t t)
+ (3 'ical:warning t t))
+ (ical:match-comment-property
+ (1 'ical:property-name t t)
+ (2 'ical:property-value t t)
+ (3 'ical:warning t t))
+ (ical:match-class-property
+ (1 'ical:property-name t t)
+ (2 'ical:keyword t t)
+ (3 'ical:warning t t))
+ (ical:match-categories-property
+ (1 'ical:property-name t t)
+ (2 'ical:property-value t t)
+ (3 'ical:warning t t))
+ (ical:match-attach-property
+ (1 'ical:property-name t t)
+ (2 'ical:property-value t t)
+ (3 'ical:warning t t)
+ (13 'ical:uri t t)
+ (14 'ical:binary-data t t))
+ (ical:match-version-property
+ (1 'ical:property-name t t)
+ (2 'ical:property-value t t)
+ (3 'ical:warning t t))
+ (ical:match-prodid-property
+ (1 'ical:property-name t t)
+ (2 'ical:property-value t t)
+ (3 'ical:warning t t))
+ (ical:match-method-property
+ (1 'ical:property-name t t)
+ (2 'ical:property-value t t)
+ (3 'ical:warning t t))
+ (ical:match-calscale-property
+ (1 'ical:property-name t t)
+ (2 'ical:keyword t t)
+ (3 'ical:warning t t)))
+ "Entries for iCalendar properties in `font-lock-keywords'.")
+
+(defconst ical:ignored-properties-font-lock-keywords
+ `((,(rx ical:other-property) (1 'ical:ignored keep)
+ (2 'ical:ignored keep)))
+ "Entries for iCalendar ignored properties in `font-lock-keywords'.")
+
+(defconst ical:components-font-lock-keywords
+ '((ical:match-vcalendar-component
+ (1 'ical:keyword t t)
+ (2 'ical:component-name t t))
+ (ical:match-other-component
+ (1 'ical:keyword t t)
+ (2 'ical:component-name t t))
+ (ical:match-valarm-component
+ (1 'ical:keyword t t)
+ (2 'ical:component-name t t))
+ (ical:match-daylight-component
+ (1 'ical:keyword t t)
+ (2 'ical:component-name t t))
+ (ical:match-standard-component
+ (1 'ical:keyword t t)
+ (2 'ical:component-name t t))
+ (ical:match-vtimezone-component
+ (1 'ical:keyword t t)
+ (2 'ical:component-name t t))
+ (ical:match-vfreebusy-component
+ (1 'ical:keyword t t)
+ (2 'ical:component-name t t))
+ (ical:match-vjournal-component
+ (1 'ical:keyword t t)
+ (2 'ical:component-name t t))
+ (ical:match-vtodo-component
+ (1 'ical:keyword t t)
+ (2 'ical:component-name t t))
+ (ical:match-vevent-component
+ (1 'ical:keyword t t)
+ (2 'ical:component-name t t)))
+ "Entries for iCalendar components in `font-lock-keywords'.")
+
+(defvar ical:font-lock-keywords
+ (append ical:params-font-lock-keywords
+ ical:properties-font-lock-keywords
+ ical:components-font-lock-keywords
+ ical:ignored-properties-font-lock-keywords)
+ "Value of `font-lock-keywords' for icalendar-mode.")
+
+\f
+;; The major mode:
+
+;;; Mode hook
+(defvar ical:mode-hook nil
+ "Hook run when activating `ical:mode'.")
+
+(add-to-list 'auto-mode-alist '("\\.ics\\'" . icalendar-mode))
+
+;;; Syntax table
+(defvar ical:mode-syntax-table
+ (let ((st (make-syntax-table)))
+ ;; Characters for which the standard syntax table suffices:
+ ;; ; (punctuation): separates some property values, and property parameters
+ ;; " (string): begins and ends string values
+ ;; : (punctuation): separates property name (and parameters) from property
+ ;; values
+ ;; , (punctuation): separates values in a list
+ ;; CR, LF (whitespace): content line endings
+ ;; space (whitespace): when at the beginning of a line, continues the
+ ;; previous line
+
+ ;; Characters which need to be adjusted from the standard syntax table:
+ ;; = is punctuation, not a symbol constituent:
+ (modify-syntax-entry ?= ". " st)
+ ;; / is punctuation, not a symbol constituent:
+ (modify-syntax-entry ?/ ". " st)
+ st)
+ "Syntax table used in `ical:mode'.")
+
+
+;;; Commands
+
+;; TODO: is there a corresponding list by mimetype for buffers
+;; displaying message parts? Thought I saw this somewhere...
+
+(defun ical:switch-to-unfolded-buffer ()
+ "Switch to viewing the contents of the current buffer in a new
+buffer where content lines have been unfolded.
+
+`Folding' means inserting a line break and a single whitespace
+character to continue lines longer than 75 octets; `unfolding'
+means removing the extra whitespace inserted by folding. The
+iCalendar standard (RFC5545) requires folding lines when
+serializing data to iCalendar format, and unfolding before
+parsing it. In icalendar-mode, folded lines may not have proper
+syntax highlighting; this command allows you to view iCalendar
+data with proper syntax highlighting, as the parser sees it.
+
+If the current buffer is visiting a file, this function will
+offer to save the buffer first, and then reload the contents from
+the file, performing unfolding with `icalendar-unfold-undecoded-region'
+before decoding it. This is the most reliable way to unfold lines.
+
+If it is not visiting a file, it will unfold the new buffer
+with `icalendar-unfold-region'. This can in some cases have
+undesirable effects (see its docstring), so the original contents
+are preserved unchanged in the current buffer.
+
+In both cases, after switching to the new buffer, this command
+offers to kill the original buffer.
+
+It is recommended to turn off `auto-fill-mode' when viewing an
+unfolded buffer, so that filling does not interfere with syntax
+highlighting. This function offers to disable `auto-fill-mode' if
+it is enabled in the new buffer; consider using
+`visual-line-mode' instead."
+ (interactive)
+ (when (and buffer-file-name (buffer-modified-p))
+ (when (y-or-n-p (format "Save before reloading from %s?"
+ (file-name-nondirectory buffer-file-name)))
+ (save-buffer)))
+ (let ((old-buffer (current-buffer))
+ (mmode major-mode)
+ (uf-buffer (if buffer-file-name
+ (ical:unfolded-buffer-from-file buffer-file-name)
+ (ical:unfolded-buffer-from-buffer (current-buffer)))))
+ (switch-to-buffer uf-buffer)
+ ;; restart original major mode, in case the new buffer is
+ ;; still in fundamental-mode: TODO: is this necessary?
+ (funcall mmode)
+ (when (y-or-n-p (format "Unfolded buffer is shown. Kill %s?"
+ (buffer-name old-buffer)))
+ (kill-buffer old-buffer))
+ (when (and auto-fill-function
+ (y-or-n-p "Disable auto-fill-mode?"))
+ (auto-fill-mode -1))))
+
+(defun ical:maybe-switch-to-unfolded-buffer ()
+ "Check for folded lines and ask for confirmation before calling
+`icalendar-switch-to-unfolded-buffer', which see.
+
+This function is intended to be run via `icalendar-mode-hook'
+when `icalendar-mode' is activated."
+ (interactive)
+ (if (ical:contains-folded-lines-p)
+ (when (y-or-n-p "Buffer contains folded lines; unfold in new buffer?")
+ (ical:switch-to-unfolded-buffer))
+ ;; No need for unfolding, just inform the user:
+ (message "Buffer does not contain any lines to unfold")))
+
+(add-hook 'ical:mode-hook 'ical:maybe-switch-to-unfolded-buffer)
+
+(defun ical:before-save-checks ()
+ "Offer to change coding system and fold content lines in the
+current buffer when saving a buffer in `icalendar-mode'.
+
+The iCalendar standard requires CR-LF line endings, so if
+`buffer-file-coding-system' does not use a coding system which
+specifies them, this command offers to switch to a corresponding
+coding system which does.
+
+`Folding' means inserting a line break and a single whitespace
+character to continue lines longer than 75 octets. The iCalendar
+standard requires folding lines when serializing data to
+iCalendar format, so if the buffer contains unfolded lines, this
+command asks you whether you want to fold them."
+ (interactive)
+ (when (eq major-mode 'ical:mode)
+ (let* ((cs buffer-file-coding-system)
+ (suggested-cs (if cs (coding-system-change-eol-conversion cs 'dos)
+ 'prefer-utf-8-dos)))
+ (when (and (not (coding-system-equal cs suggested-cs))
+ (y-or-n-p
+ (format "Current coding system %s does not use CR-LF line endings. Change to %s for save?" cs suggested-cs)))
+ (set-buffer-file-coding-system suggested-cs))
+ (when (and (ical:contains-unfolded-lines-p)
+ (y-or-n-p "Fold content lines before saving?"))
+ (ical:fold-region (point-min) (point-max))))))
+
+(add-hook 'before-save-hook 'ical:before-save-checks)
+
+;;; Mode definition
+(define-derived-mode ical:mode text-mode "iCalendar"
+ "Major mode for viewing and editing iCalendar (RFC5545) data.
+
+This mode provides syntax highlighting for iCalendar components,
+properties, values, and property parameters, and commands to deal
+with folding and unfolding iCalendar content lines.
+
+`Folding' means inserting whitespace characters to continue long
+lines; `unfolding' means removing the extra whitespace inserted
+by folding. The iCalendar standard requires folding lines when
+serializing data to iCalendar format, and unfolding before
+parsing it.
+
+Thus icalendar-mode's syntax highlighting is designed to work with
+unfolded lines. When icalendar-mode is activated, it will offer to
+unfold lines; see `icalendar-switch-to-unfolded-buffer'. It will also
+offer to fold lines when saving a buffer to a file; see
+`icalendar-before-save-checks'. That function also offers to convert the
+line endings in the file to CR-LF, as the standard requires."
+ :group 'icalendar
+ :syntax-table ical:mode-syntax-table
+ ;; TODO: Keymap?
+ ;; TODO: buffer-local variables?
+ ;; TODO: indent-line-function and indentation variables
+ ;; TODO: mode-specific menu and context menus
+ ;; TODO: eldoc integration
+ ;; TODO: completion of keywords
+ ;; TODO: hook for folding in change-major-mode-hook?
+ (progn
+ (setq font-lock-defaults '(ical:font-lock-keywords nil t))))
+
+(provide 'icalendar-mode)
+
+;; Local Variables:
+;; read-symbol-shorthands: (("ical:" . "icalendar-"))
+;; End:
+;;; icalendar-mode.el ends here
diff --git a/lisp/calendar/icalendar-parser.el b/lisp/calendar/icalendar-parser.el
index bc9524ff389..08cc4dbd0fb 100644
--- a/lisp/calendar/icalendar-parser.el
+++ b/lisp/calendar/icalendar-parser.el
@@ -41,12 +41,7 @@
;; standard as type symbols. These type symbols store all the metadata
;; about the relevant types, and are used for type-based dispatch in the
;; parser and printer functions. In the abstract syntax tree, each node
-;; contains a type symbol naming its type.
-;;
-;; The regular expressions defined by the `ical:define-*' macros are
-;; also used to create entries for `font-lock-keywords', which are
-;; gathered into several constants along the way, and used to provide
-;; syntax highlighting in icalendar-mode.el. A number of other regular
+;; contains a type symbol naming its type. A number of other regular
;; expressions which encode basic categories of the grammar are also
;; defined in this file.
;;
@@ -1619,9 +1614,6 @@ ical:utc-offset
\f
;;; Section 3.2: Property Parameters
-(defconst ical:params-font-lock-keywords nil ;; populated by ical:define-param
- "Entries for iCalendar property parameters in `font-lock-keywords'.")
-
(defconst ical:param-types nil ;; populated by ical:define-param
"Alist mapping printed parameter names to type symbols")
@@ -1756,7 +1748,6 @@ ical:altrepparam
"Alternate text representation (URI)"
ical:uri
:quoted t
- :value-face ical:uri
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.2.1")
(ical:define-param ical:cnparam "CN"
@@ -1778,7 +1769,6 @@ ical:cutypeparam
;; don't recognize the same way as they would the UNKNOWN
;; value":
:unrecognized "UNKNOWN"
- :value-face ical:keyword
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.2.3")
(ical:define-param ical:delfromparam "DELEGATED-FROM"
@@ -1791,7 +1781,6 @@ ical:delfromparam
ical:cal-address
:quoted t
:list-sep ","
- :value-face ical:uri
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.2.4")
(ical:define-param ical:deltoparam "DELEGATED-TO"
@@ -1804,7 +1793,6 @@ ical:deltoparam
ical:cal-address
:quoted t
:list-sep ","
- :value-face ical:uri
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.2.5")
(ical:define-param ical:dirparam "DIR"
@@ -1816,7 +1804,6 @@ ical:dirparam
user which is the value of the property."
ical:uri
:quoted t
- :value-face ical:uri
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.2.6")
(ical:define-param ical:encodingparam "ENCODING"
@@ -1827,7 +1814,6 @@ ical:encodingparam
is \"BINARY\"."
(or "8BIT" "BASE64")
:default "8BIT"
- :value-face ical:keyword
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.2.7")
(rx-define ical:mimetype
@@ -1867,7 +1853,6 @@ ical:fbtypeparam
ical:x-name
ical:iana-token)
:default "BUSY"
- :value-face ical:keyword
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.2.9")
;; TODO: see https://www.rfc-editor.org/rfc/rfc5646#section-2.1
@@ -1893,7 +1878,6 @@ ical:memberparam
ical:cal-address
:quoted t
:list-sep ","
- :value-face ical:uri
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.2.11")
(ical:define-param ical:partstatparam "PARTSTAT"
@@ -1925,7 +1909,6 @@ ical:partstatparam
;; they don't recognize the same way as they would the
;; NEEDS-ACTION value."
:default "NEEDS-ACTION"
- :value-face ical:keyword
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.2.12")
(ical:define-param ical:rangeparam "RANGE"
@@ -1936,7 +1919,6 @@ ical:rangeparam
legacy applications might also produce \"THISANDPRIOR\"."
"THISANDFUTURE"
:default "THISANDFUTURE"
- :value-face ical:keyword
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.2.13")
(ical:define-param ical:trigrelparam "RELATED"
@@ -1948,7 +1930,6 @@ ical:trigrelparam
the start of the component; similarly for \"END\"."
(or "START" "END")
:default "START"
- :value-face ical:keyword
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.2.14")
(ical:define-param ical:reltypeparam "RELTYPE"
@@ -1968,7 +1949,6 @@ ical:reltypeparam
;; "Applications MUST treat x-name and iana-token values they don't
;; recognize the same way as they would the PARENT value."
:default "PARENT"
- :value-face ical:keyword
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.2.15")
(ical:define-param ical:roleparam "ROLE"
@@ -1991,7 +1971,6 @@ ical:roleparam
;; they don't recognize the same way as they would the
;; REQ-PARTICIPANT value."
:default "REQ-PARTICIPANT"
- :value-face ical:keyword
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.2.16")
(ical:define-param ical:rsvpparam "RSVP"
@@ -2002,7 +1981,6 @@ ical:rsvpparam
the Organizer of a VEVENT or VTODO."
ical:boolean
:default "FALSE"
- :value-face ical:keyword
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.2.17")
(ical:define-param ical:sentbyparam "SENT-BY"
@@ -2019,7 +1997,6 @@ ical:sentbyparam
;; have the same print name.
ical:cal-address
:quoted t
- :value-face ical:uri
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.2.18")
(ical:define-param ical:tzidparam "TZID"
@@ -2107,7 +2084,6 @@ ical:valuetypeparam
containing property's value, if it is not of the default value
type."
ical:printed-value-type
- :value-face ical:keyword
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.2.20")
(ical:define-param ical:otherparam nil ; don't add to ical:param-types
@@ -2117,9 +2093,7 @@ ical:otherparam
parameters with an unknown name (matching rx `icalendar-param-name')
whose values must be parsed and preserved but not further
interpreted."
- ical:param-value
- :name-face font-lock-comment-face
- :value-face font-lock-comment-face)
+ ical:param-value)
(rx-define ical:other-param-safe
;; we use this rx to skip params when matching properties and
@@ -2134,10 +2108,6 @@ ical:other-param-safe
;;; Properties:
-(defconst ical:properties-font-lock-keywords
- nil ;; populated by ical:define-property
- "Entries for iCalendar properties in `font-lock-keywords'.")
-
(defconst ical:property-types nil ;; populated by ical:define-property
"Alist mapping printed property names to type symbols")
@@ -2373,7 +2343,6 @@ ical:calscale
"GREGORIAN"
:default "GREGORIAN"
:child-spec (:zero-or-more (ical:otherparam))
- :value-face ical:keyword
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.7.1")
(ical:define-property ical:method "METHOD"
@@ -2440,8 +2409,6 @@ ical:attach
ical:encodingparam)
:zero-or-more (ical:otherparam))
:other-validator ical:attach-validator
- :extra-faces ((13 'ical:uri t t)
- (14 'ical:binary-data t t))
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.8.1.1")
(defun ical:attach-validator (node)
@@ -2504,7 +2471,6 @@ ical:class
:default "PUBLIC"
:unrecognized "PRIVATE"
:child-spec (:zero-or-more (ical:otherparam))
- :value-face ical:keyword
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.8.1.3")
(ical:define-property ical:comment "COMMENT"
@@ -2566,7 +2532,6 @@ ical:geo
the equator if negative. The longitude value is east of the prime
meridian if positive, and west of it if negative."
ical:geo-coordinates
- :value-face ical:numeric-types
:child-spec (:zero-or-more (ical:otherparam))
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.8.1.6")
@@ -2595,7 +2560,6 @@ ical:percent-complete
enforced here)."
ical:integer
:child-spec (:zero-or-more (ical:otherparam))
- :value-face ical:numeric-types
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.8.1.8")
;; TODO: type for priority values?
@@ -2609,7 +2573,6 @@ ical:priority
ical:integer
:default "0"
:child-spec (:zero-or-more (ical:otherparam))
- :value-face ical:numeric-types
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.8.1.9")
(ical:define-property ical:resources "RESOURCES"
@@ -2650,7 +2613,6 @@ ical:status
at most once on these components."
ical:status-keyword
:child-spec (:zero-or-more (ical:otherparam))
- :value-face ical:keyword
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.8.1.11")
(ical:define-property ical:summary "SUMMARY"
@@ -2675,7 +2637,6 @@ ical:completed
an `icalendar-vtodo' was actually completed. The value must be an
`icalendar-date-time' with a UTC time."
ical:date-time
- :value-face ical:date-time-types
:child-spec (:zero-or-more (ical:otherparam))
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.8.2.1")
@@ -2694,7 +2655,6 @@ ical:dtend
:other-types (ical:date)
:child-spec (:zero-or-one (ical:valuetypeparam ical:tzidparam)
:zero-or-more (ical:otherparam))
- :value-face ical:date-time-types
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.8.2.2")
(ical:define-property ical:due "DUE"
@@ -2712,7 +2672,6 @@ ical:due
:other-types (ical:date)
:child-spec (:zero-or-one (ical:valuetypeparam ical:tzidparam)
:zero-or-more (ical:otherparam))
- :value-face ical:date-time-types
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.8.2.3")
(ical:define-property ical:dtstart "DTSTART"
@@ -2739,7 +2698,6 @@ ical:dtstart
:other-types (ical:date)
:child-spec (:zero-or-one (ical:valuetypeparam ical:tzidparam)
:zero-or-more (ical:otherparam))
- :value-face ical:date-time-types
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.8.2.4")
(ical:define-property ical:duration "DURATION"
@@ -2757,7 +2715,6 @@ ical:duration
value, then the duration must be given as a number of weeks or days."
ical:dur-value
:child-spec (:zero-or-more (ical:otherparam))
- :value-face ical:date-time-types
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.8.2.5")
(ical:define-property ical:freebusy "FREEBUSY"
@@ -2771,7 +2728,6 @@ ical:freebusy
:list-sep ","
:child-spec (:zero-or-one (ical:fbtypeparam)
:zero-or-more (ical:otherparam))
- :value-face ical:date-time-types
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.8.2.6")
(ical:define-property ical:transp "TRANSP"
@@ -2786,7 +2742,6 @@ ical:transp
"OPAQUE")
:default "OPAQUE"
:child-spec (:zero-or-more (ical:otherparam))
- :value-face ical:keyword
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.8.2.7")
;;;;; Section 3.8.3: Time Zone Component Properties
@@ -2824,7 +2779,6 @@ ical:tzoffsetfrom
UTC)."
ical:utc-offset
:child-spec (:zero-or-more (ical:otherparam))
- :value-face ical:date-time-types
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.8.3.3")
(ical:define-property ical:tzoffsetto "TZOFFSETTO"
@@ -2839,7 +2793,6 @@ ical:tzoffsetto
the prime meridian (behind UTC)."
ical:utc-offset
:child-spec (:zero-or-more (ical:otherparam))
- :value-face ical:date-time-types
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.8.3.4")
(ical:define-property ical:tzurl "TZURL"
@@ -2849,7 +2802,6 @@ ical:tzurl
`icalendar-vtimezone' component are published."
ical:uri
:child-spec (:zero-or-more (ical:otherparam))
- :value-face ical:uri
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.8.3.5")
;;;;; Section 3.8.4: Relationship Component Properties
@@ -2885,7 +2837,6 @@ ical:attendee
ical:dirparam
ical:languageparam)
:zero-or-more (ical:otherparam))
- :value-face ical:uri
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.8.4.1")
(ical:define-property ical:contact "CONTACT"
@@ -2915,7 +2866,6 @@ ical:organizer
ical:sentbyparam
ical:languageparam)
:zero-or-more (ical:otherparam))
- :value-face ical:uri
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.8.4.3")
(ical:define-property ical:recurrence-id "RECURRENCE-ID"
@@ -2937,7 +2887,6 @@ ical:recurrence-id
ical:tzidparam
ical:rangeparam)
:zero-or-more (ical:otherparam))
- :value-face ical:date-time-types
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.8.4.4")
(ical:define-property ical:related-to "RELATED-TO"
@@ -2961,7 +2910,6 @@ ical:url
`icalendar-vfreebusy' component."
ical:uri
:child-spec (:zero-or-more (ical:otherparam))
- :value-face ical:uri
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.8.4.6")
;; TODO: UID should probably be its own type
@@ -2998,7 +2946,6 @@ ical:exdate
:list-sep ","
:child-spec (:zero-or-one (ical:valuetypeparam ical:tzidparam)
:zero-or-more (ical:otherparam))
- :value-face ical:date-time-types
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.8.5.1")
(ical:define-property ical:rdate "RDATE"
@@ -3018,7 +2965,6 @@ ical:rdate
:list-sep ","
:child-spec (:zero-or-one (ical:valuetypeparam ical:tzidparam)
:zero-or-more (ical:otherparam))
- :value-face ical:date-time-types
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.8.5.2")
(ical:define-property ical:rrule "RRULE"
@@ -3033,7 +2979,6 @@ ical:rrule
ical:recur
;; TODO: faces for subexpressions?
:child-spec (:zero-or-more (ical:otherparam))
- :value-face ical:recurrence-rule
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.8.5.3")
;;;;; Section 3.8.6: Alarm Component Properties
@@ -3052,7 +2997,6 @@ ical:action
ical:x-name)))
:default-type ical:text
:child-spec (:zero-or-more (ical:otherparam))
- :value-face ical:keyword
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.8.6.1")
(ical:define-property ical:repeat "REPEAT"
@@ -3065,7 +3009,6 @@ ical:repeat
ical:integer
:default 0
:child-spec (:zero-or-more (ical:otherparam))
- :value-face ical:numeric-types
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.8.6.2")
(ical:define-property ical:trigger "TRIGGER"
@@ -3087,7 +3030,6 @@ ical:trigger
:child-spec (:zero-or-one (ical:valuetypeparam ical:trigrelparam)
:zero-or-more (ical:otherparam))
:other-validator ical:trigger-validator
- :value-face ical:date-time-types
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.8.6.3")
(defun ical:trigger-validator (node)
@@ -3130,7 +3072,6 @@ ical:created
in UTC time."
ical:date-time
:child-spec (:zero-or-more (ical:otherparam))
- :value-face ical:date-time-types
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.8.7.1")
(ical:define-property ical:dtstamp "DTSTAMP"
@@ -3154,7 +3095,6 @@ ical:dtstamp
The value must be in UTC time."
ical:date-time
:child-spec (:zero-or-more (ical:otherparam))
- :value-face ical:date-time-types
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.8.7.2")
(ical:define-property ical:last-modified "LAST-MODIFIED"
@@ -3165,7 +3105,6 @@ ical:last-modified
was last modified in the calendar database."
ical:date-time
:child-spec (:zero-or-more (ical:otherparam))
- :value-face ical:date-time-types
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.8.7.3")
(ical:define-property ical:sequence "SEQUENCE"
@@ -3180,7 +3119,6 @@ ical:sequence
ical:integer
:default 0
:child-spec (:zero-or-more (ical:otherparam))
- :value-face ical:numeric-types
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.8.7.4")
;;;;; Section 3.8.8: Miscellaneous Component Properties
@@ -3204,11 +3142,6 @@ ical:other-property
:child-spec (:allow-others t)
:link "https://www.rfc-editor.org/rfc/rfc5545#section-3.8.8")
-(defconst ical:ignored-properties-font-lock-keywords
- `((,(rx ical:other-property) (1 'ical:ignored keep)
- (2 'ical:ignored keep)))
- "Entries for iCalendar ignored properties in `font-lock-keywords'.")
-
(defun ical:read-req-status-info (s)
"Read a request status value from S.
S should have been previously matched against `icalendar-request-status-info'."
@@ -3281,10 +3214,6 @@ ical:request-status
\f
;;; Section 3.6: Calendar Components
-(defconst ical:components-font-lock-keywords
- nil ;; populated by ical:define-component
- "Entries for iCalendar components in `font-lock-keywords'.")
-
(defconst ical:component-types nil ;; populated by ical:define-component
"Alist mapping printed component names to type symbols")
--
2.39.5
prev parent reply other threads:[~2024-12-20 19:53 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-12-20 13:07 bug#74994: Improve Emacs iCalendar support Richard Lawrence
2024-12-20 19:47 ` bug#74994: [PATCH 1/2] New parser for iCalendar (RFC5545) Richard Lawrence
2024-12-20 19:53 ` Richard Lawrence [this message]
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
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=87y10aku5u.fsf@recursewithless.net \
--to=rwl@recursewithless.net \
--cc=74994@debbugs.gnu.org \
/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 external index
https://git.savannah.gnu.org/cgit/emacs.git
https://git.savannah.gnu.org/cgit/emacs/org-mode.git
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.