branch: elpa/subed
commit 10b85e48a06fcc7a70d02a516e2886ea2936d4bf
Author: Sacha Chua <[email protected]>
Commit: Sacha Chua <[email protected]>

    subed-vtt: Improve ID and comment handling
    
    * subed/subed-common.el (jump-to-subtitle-comment): New.
    (forward-subtitle-comment): New.
    (backward-subtitle-comment): New.
    (subtitle-comment): New.
    (set-subtitle-comment): New.
    (subed-subtitle-list-text): Check for empty comment.
    * subed/subed-vtt.el (subed--subtitle-comment): Move implementation to
    comment section, tweak how comments are recognized.
    (subed--jump-to-subtitle-id): Jump to ID if specified.
    (subed--forward-subtitle-id): Look for the ID or timestamp.
    (subed--backward-subtitle-id): Look for the ID or timestamp.
    (subed--jump-to-subtitle-comment): New.
    (subed--set-subtitle-comment): New.
    (subed--prepend-subtitle): Insert before the comment or ID.
    (subed--append-subtitle): Insert before the comment or ID.
    * tests/test-subed-vtt.el: Add tests for comments and IDs.
---
 subed/subed-common.el   |  37 ++++++-
 subed/subed-vtt.el      |  94 +++++++++++------
 tests/test-subed-vtt.el | 262 +++++++++++++++++++++++++++++++++++++++++++-----
 3 files changed, 337 insertions(+), 56 deletions(-)

diff --git a/subed/subed-common.el b/subed/subed-common.el
index 017e3af542..c3dd0ab104 100644
--- a/subed/subed-common.el
+++ b/subed/subed-common.el
@@ -218,6 +218,12 @@ If SUB-ID is not given, use subtitle on point.
 Return point or nil if a the subtitle's text can't be found."
   (interactive))
 
+(subed-define-generic-function jump-to-subtitle-comment (&optional sub-id)
+  "Move point on the first character of subtitle's comment.
+If SUB-ID is not given, use subtitle on point.
+Return point or nil if a the subtitle's comment can't be found."
+  (interactive))
+
 (subed-define-generic-function jump-to-subtitle-end (&optional sub-id)
   "Move point after the last character of the subtitle's text.
 If SUB-ID is not given, use subtitle on point.
@@ -265,6 +271,28 @@ Return point or nil if there is no previous subtitle."
   (when (subed-backward-subtitle-id)
     (subed-jump-to-subtitle-text)))
 
+(subed-define-generic-function forward-subtitle-comment ()
+  "Move point to next subtitle's comment.
+Return point or nil if there is no next subtitle."
+  (interactive)
+  (let ((pos (point)))
+    (if (and (subed-forward-subtitle-id)
+             (subed-jump-to-subtitle-comment))
+        (point)
+      (goto-char pos)
+      nil)))
+
+(subed-define-generic-function backward-subtitle-comment ()
+  "Move point to previous subtitle's comment.
+Return point or nil if there is no previous subtitle."
+  (interactive)
+  (let ((pos (point)))
+    (if (and (subed-backward-subtitle-id)
+             (subed-jump-to-subtitle-comment))
+        (point)
+      (goto-char pos)
+      nil)))
+
 (subed-define-generic-function forward-subtitle-end ()
   "Move point to end of next subtitle.
 Return point or nil if there is no next subtitle."
@@ -350,7 +378,12 @@ If SUB-ID is not given, set the text of the current 
subtitle."
       (- (point) start-point))))
 
 (subed-define-generic-function subtitle-comment (&optional _)
-  "Return the comment preceding this subtitle."
+  "Return subtitle comment or an empty string."
+  nil)
+
+(subed-define-generic-function set-subtitle-comment (comment &optional _)
+   "Set the current subtitle's comment to COMMENT.
+If COMMENT is nil or the empty string, remove the comment."
   nil)
 
 (subed-define-generic-function set-subtitle-time-start (msecs
@@ -609,7 +642,7 @@ If INCLUDE-COMMENTS is a function, call the function on 
comments
 before including them."
   (mapconcat
    (lambda (sub)
-     (if (and include-comments (elt sub 4))
+     (if (and include-comments (elt sub 4) (not (string= (elt sub 4) "")))
          (concat "\n"
                  (if (functionp include-comments)
                      (funcall include-comments (elt sub 4))
diff --git a/subed/subed-vtt.el b/subed/subed-vtt.el
index 93d4844277..f8b5a232fe 100644
--- a/subed/subed-vtt.el
+++ b/subed/subed-vtt.el
@@ -94,18 +94,6 @@ format-specific function for MAJOR-MODE."
         (unless (subed-forward-subtitle-id)
           (throw 'subtitle-id nil))))))
 
-(cl-defmethod subed--subtitle-comment (&context (major-mode subed-vtt-mode) 
&optional sub-id)
-  "Return the comment or comments before the current subtitle.
-If SUB-ID is specified, jump to that subtitle first.
-Use the format-specific function for MAJOR-MODE."
-  (save-excursion
-    (subed-jump-to-subtitle-id sub-id)
-    (let ((sub-start-point (point)))
-      (or (subed-backward-subtitle-end)
-          (goto-char (point-min)))
-      (when (re-search-forward "^\\(NOTE\\(.*\n\\)+\n+\\)" sub-start-point t)
-        (match-string 0)))))
-
 ;;; Traversing
 
 (cl-defmethod subed--jump-to-subtitle-id (&context (major-mode subed-vtt-mode) 
&optional sub-id)
@@ -119,14 +107,13 @@ Use the format-specific function for MAJOR-MODE."
         ;; Look for a line that contains the timestamp, preceded by one or more
         ;; blank lines or the beginning of the buffer.
         (let* ((regex (concat "\\(" subed--regexp-separator "\\|\\`\\)\\("
-                              (regexp-quote sub-id) "\\)")))
+                              (regexp-quote sub-id) "\\)[ \n]")))
           (goto-char (point-min))
           (setq found (re-search-forward regex nil t))
           (if found
               (goto-char (match-beginning 2))
             (goto-char orig-point)
             nil))
-
       ;; Find two or more blank lines or the beginning of the buffer, followed
       ;; by an optional line and a timestamp.
       (or (and (re-search-backward subed--regexp-separator nil t)
@@ -138,6 +125,17 @@ Use the format-specific function for MAJOR-MODE."
         (point))
        ((looking-at (concat "\\(.*\\)\n" subed-vtt--regexp-timestamp " *--> *" 
subed-vtt--regexp-timestamp " *\n"))
         (point))
+       ((looking-at "^NOTE[ \n]")
+        ;; At a subtitle's comment; scan forward for the timestamp
+        (if (re-search-forward
+             (concat
+              subed--regexp-separator
+              (concat "\\(.*\n\\)?" subed-vtt--regexp-timestamp " *--> *" 
subed-vtt--regexp-timestamp " *\n")) nil t)
+            (progn
+              (goto-char (or (match-beginning 1) (match-beginning 2)))
+              (point))
+          (goto-char orig-point)
+          nil))
        (t
         (goto-char orig-point)
         nil)))))
@@ -200,11 +198,13 @@ can be found.  Use the format-specific function for 
MAJOR-MODE."
 Return point or nil if there is no next subtitle.  Use the
 format-specific function for MAJOR-MODE."
   (let ((orig-point (point)))
-    (if (and
-         (re-search-forward subed--regexp-separator nil t)
-         (re-search-forward (concat "^" subed--regexp-timestamp
-                                    " *--> *" subed--regexp-timestamp)
-                            nil t))
+    (subed-jump-to-subtitle-end)
+    (when (and (bolp) (> (point) (point-min))) (forward-char -1))
+    (if (re-search-forward (concat subed--regexp-separator
+                                   "\\(.*?\n\\)?"
+                                   subed--regexp-timestamp
+                                   " *--> *" subed--regexp-timestamp)
+                           nil t)
         (or (subed-jump-to-subtitle-id)
             (progn
               (goto-char orig-point)
@@ -220,7 +220,11 @@ format-specific function for MAJOR-MODE."
     (subed-jump-to-subtitle-id)
     (or
      (catch 'found
-       (while (re-search-backward subed--regexp-separator nil t)
+       (while (re-search-backward
+               (concat "^\\(.*?\\n\\)?"
+                       subed--regexp-timestamp
+                       " *--> *" subed--regexp-timestamp)
+               nil t)
          (when (subed-jump-to-subtitle-id)
            (throw 'found (point)))))
      (progn (goto-char orig-point) nil))))
@@ -240,6 +244,46 @@ Make sure COMMENT ends with a blank line."
         ((string-match "\n" comment) (concat "NOTE\n" comment "\n\n"))
         (t (concat "NOTE " comment "\n\n"))))
 
+(cl-defmethod subed--jump-to-subtitle-comment (&context (major-mode 
subed-vtt-mode)
+                                                        &optional sub-id)
+  "Move point to subtitle's comment.
+If SUB-ID is not given, use subtitle on point.
+Return point, or nil if no comment could be found.
+Use the format-specific function for MAJOR-MODE."
+  (let ((pos (point)))
+    (if (and (subed-jump-to-subtitle-id sub-id)
+             (re-search-backward "^NOTE"
+                                 (or (save-excursion 
(subed-backward-subtitle-end)) (point-min)) t))
+        (progn (goto-char (match-beginning 0))
+               (point))
+      (goto-char pos)
+      nil)))
+
+(cl-defmethod subed--subtitle-comment (&context (major-mode subed-vtt-mode)
+                                                &optional sub-id)
+  "Return subtitle comment or an empty string.
+If SUB-ID is not given, use the subtitle on point.
+Use the format-specific function for MAJOR-MODE."
+  (save-excursion
+    (let ((comment-start (subed-jump-to-subtitle-comment sub-id)))
+      (if comment-start
+          (buffer-substring comment-start (subed-jump-to-subtitle-id sub-id))
+        ""))))
+
+(cl-defmethod subed--set-subtitle-comment (comment &context (major-mode 
subed-vtt-mode)
+                                                   &optional sub-id)
+  "Set the current subtitle's comment to COMMENT.
+If COMMENT is nil or the empty string, remove the comment.
+If SUB-ID is not given, use the subtitle on point.
+Use the format-specific function for MAJOR-MODE."
+  (let ((comment-start (subed-jump-to-subtitle-comment sub-id)))
+    ;; remove previous comment
+    (if comment-start
+        (delete-region comment-start (subed-jump-to-subtitle-id sub-id))
+      (subed-jump-to-subtitle-id sub-id))
+    (when (and comment (not (string= comment "")))
+      (insert (subed-vtt--format-comment comment)))))
+
 (cl-defmethod subed--make-subtitle (&context (major-mode subed-vtt-mode)
                                              &optional _ start stop text 
comment)
   "Generate new subtitle string.
@@ -268,7 +312,7 @@ TEXT defaults to an empty string.
 
 Move point to the text of the inserted subtitle.  Return new
 point.  Use the format-specific function for MAJOR-MODE."
-  (subed-jump-to-subtitle-id)
+  (or (subed-jump-to-subtitle-comment) (subed-jump-to-subtitle-id))
   (insert (subed-make-subtitle id start stop text comment))
   (when (looking-at (concat "\\([[:space:]]*\\|^\\)" subed--regexp-timestamp))
     (insert "\n"))
@@ -285,13 +329,7 @@ TEXT defaults to an empty string.
 Move point to the text of the inserted subtitle.  Return new
 point.  Use the format-specific function for MAJOR-MODE."
   (let ((pos (point)))
-    (if (subed-forward-subtitle-id)
-        ;; Insert before any comments
-        (progn
-          (subed-backward-subtitle-end)
-          (cond
-           ((eobp) (insert "\n\n"))
-           ((looking-at " *\n\n") (goto-char (match-end 0)))))
+    (unless (or (subed-forward-subtitle-comment) (subed-forward-subtitle-id))
       ;; Point is on last subtitle or buffer is empty
       (subed-jump-to-subtitle-end)
       (when (looking-at "[[:space:]]+")
diff --git a/tests/test-subed-vtt.el b/tests/test-subed-vtt.el
index 06528fee92..b49a21ed47 100644
--- a/tests/test-subed-vtt.el
+++ b/tests/test-subed-vtt.el
@@ -216,24 +216,120 @@ Baz.
          (expect (looking-at subed--regexp-timestamp) :to-be t)
          (expect (match-string 0) :to-equal "00:01:01.000")))
       )
-    (describe "to specific subtitle by timestamp"
-      (it "returns timestamp's point if wanted time exists."
-        (with-temp-vtt-buffer
-         (insert mock-vtt-data)
-         (goto-char (point-max))
-         (expect (subed-jump-to-subtitle-id "00:02:02.234") :to-equal 45)
-         (expect (looking-at (regexp-quote "00:02:02.234")) :to-be t)
-         (expect (subed-jump-to-subtitle-id "00:01:01.000") :to-equal 9)
-         (expect (looking-at (regexp-quote "00:01:01.000")) :to-be t)))
-      (it "returns nil and does not move if wanted ID does not exists."
-        (with-temp-vtt-buffer
-         (insert mock-vtt-data)
-         (goto-char (point-min))
-         (search-forward "Foo")
-         (let ((stored-point (point)))
-           (expect (subed-jump-to-subtitle-id "0:08:00") :to-equal nil)
-           (expect stored-point :to-equal (point)))))
-      )
+    (describe "to subtitle ID"
+      (describe "in the current subtitle"
+        (it "returns nil in the header."
+          (with-temp-vtt-buffer
+           (insert mock-vtt-data)
+           (goto-char (point-min))
+           (expect (subed-jump-to-subtitle-id) :to-be nil)))
+        (it "goes to the ID if specified."
+          (with-temp-vtt-buffer
+           (insert "WEBVTT
+
+1
+00:01:01.000 --> 00:01:05.123
+Foo.
+
+00:02:02.234 --> 00:02:10.345
+Bar.
+")
+           (re-search-backward "Foo")
+           (expect (subed-jump-to-subtitle-id) :not :to-be nil)
+           (expect (looking-at "1") :to-be t)))
+        (it "goes to the timestamp if there is no ID."
+          (with-temp-vtt-buffer
+           (insert "WEBVTT
+
+1
+00:01:01.000 --> 00:01:05.123
+Foo.
+
+00:02:02.234 --> 00:02:10.345
+Bar.
+")
+           (re-search-backward "Bar")
+           (expect (subed-jump-to-subtitle-id) :not :to-be nil)
+           (expect (looking-at "00:02:02.234") :to-be t)))
+        (describe "when called from a comment"
+          (it "goes to the ID of the subtitle after the comment."
+                                         (with-temp-vtt-buffer
+                                          (insert "WEBVTT
+
+NOTE
+This is a comment
+
+1
+00:01:01.000 --> 00:01:05.123
+Foo.
+
+00:02:02.234 --> 00:02:10.345
+Bar.
+")
+                                          (re-search-backward "This is a 
comment")
+                                          (expect (subed-jump-to-subtitle-id) 
:not :to-be nil)
+                                          (expect (looking-at "1\n") :to-be 
t)))
+          (it "goes to the timestamp of the subtitle after the comment if no 
ID is specified."
+                                         (with-temp-vtt-buffer
+                                          (insert "WEBVTT
+
+1
+00:01:01.000 --> 00:01:05.123
+Foo.
+
+NOTE
+This is a comment
+
+00:02:02.234 --> 00:02:10.345
+Bar.
+")
+                                          (re-search-backward "This is a 
comment")
+                                          (expect (subed-jump-to-subtitle-id) 
:not :to-be nil)
+                                          (expect (looking-at (regexp-quote 
"00:02:02.234")) :to-be t)))))
+      (describe "when given an ID"
+        (it "returns ID's point if wanted time exists."
+          (with-temp-vtt-buffer
+           (insert "WEBVTT
+
+1
+00:01:01.000 --> 00:01:05.123
+Foo.
+
+NOTE
+This is a comment
+
+00:02:02.234 --> 00:02:10.345
+Bar.
+")
+           (goto-char (point-max))
+           (expect (subed-jump-to-subtitle-id "1") :not :to-be nil)
+           (expect (looking-at "1\n") :to-be t)))
+        (it "returns nil and does not move if wanted ID does not exists."
+          (with-temp-vtt-buffer
+           (insert mock-vtt-data)
+           (goto-char (point-min))
+           (search-forward "Foo")
+           (let ((stored-point (point)))
+             (expect (subed-jump-to-subtitle-id "3") :to-equal nil)
+             (expect stored-point :to-equal (point))))))
+      (describe "when given a timestamp"
+        (it "returns timestamp's point if wanted time exists."
+          (with-temp-vtt-buffer
+           (insert mock-vtt-data)
+           (goto-char (point-max))
+           (expect (subed-jump-to-subtitle-id "00:02:02.234") :to-equal 45)
+           (expect (looking-at (regexp-quote "00:02:02.234")) :to-be t)
+           (expect (subed-jump-to-subtitle-id "00:01:01.000") :to-equal 9)
+           (expect (looking-at (regexp-quote "00:01:01.000")) :to-be t)))
+        (it "returns nil and does not move if wanted ID does not exists."
+          (with-temp-vtt-buffer
+           (insert mock-vtt-data)
+           (goto-char (point-min))
+           (search-forward "Foo")
+           (let ((stored-point (point)))
+             (expect (subed-jump-to-subtitle-id "0:08:00") :to-equal nil)
+             (expect stored-point :to-equal (point)))))))
+
     (describe "to subtitle start time"
       (it "returns start time's point if movement was successful."
         (with-temp-vtt-buffer
@@ -575,8 +671,7 @@ Baz.
          (expect (looking-at (regexp-quote "00:01:01.000")) :to-be t)
          (expect (subed-backward-subtitle-time-stop) :to-be nil)
          (expect (looking-at (regexp-quote "00:01:01.000")) :to-be t)))
-      )
-    )
+      ))
 
   (describe "Setting start/stop time"
     (it "of current subtitle updates it."
@@ -848,7 +943,8 @@ Baz.
                              "00:00:05.000 --> 00:00:06.000\n"
                              "Bar.\n"))
              (subed-jump-to-subtitle-time-start "00:00:01.000")
-             (expect (subed-append-subtitle) :to-equal 67)
+             (subed-append-subtitle)
+             (expect (point) :to-equal 67)
              (expect (buffer-string) :to-equal (concat "00:00:01.000 --> 
00:00:02.000\n"
                                                        "Foo.\n\n"
                                                        "00:00:00.000 --> 
00:00:01.000\n"
@@ -1289,9 +1385,9 @@ Baz.
       (with-temp-vtt-buffer
        (expect (subed-msecs-to-timestamp 1401) :to-equal "00:00:01.401"))))
   (describe "Working with comments"
-    (it "ignores the comment when jumping to the end of the subtitle"
-      (with-temp-vtt-buffer
-       (insert "WEBVTT
+    (before-each
+      (setq mock-vtt-comments-data
+            "WEBVTT
 
 00:00:00.000 --> 00:00:01.000
 This is a test.
@@ -1301,13 +1397,127 @@ and have more text as needed.
 
 00:01:00.000 --> 00:00:02.000
 This is another test here.
-")
+"))
+    (it "ignores the comment when jumping to the end of the subtitle"
+      (with-temp-vtt-buffer
+       (insert mock-vtt-comments-data)
        (goto-char (point-min))
        (subed-forward-subtitle-end)
        (expect (current-word) :to-equal "test")
        (subed-forward-subtitle-end)
-       (expect (current-word) :to-equal "here"))))
+       (expect (current-word) :to-equal "here")))
+    (describe "jumping to the comment"
+      (it "returns nil when there is no comment."
+        (with-temp-vtt-buffer
+         (insert mock-vtt-comments-data)
+         (re-search-backward "This is a test")
+         (expect (subed-jump-to-subtitle-comment) :to-be nil)))
+      (it "jumps to the comment for the current subtitle."
+        (with-temp-vtt-buffer
+         (insert mock-vtt-comments-data)
+         (goto-char (point-max))
+         (subed-jump-to-subtitle-comment)
+         (expect (looking-at "NOTE A comment") :to-be t))))
+    (describe "getting the comment"
+      (it "returns an empty string when there is no comment."
+        (with-temp-vtt-buffer
+         (insert mock-vtt-comments-data)
+         (re-search-backward "This is a test")
+         (expect (subed-subtitle-comment) :to-equal "")))
+      (it "returns the comment."
+        (with-temp-vtt-buffer
+         (insert mock-vtt-comments-data)
+         (goto-char (point-max))
+         (expect (subed-subtitle-comment) :to-equal "NOTE A comment can go 
here\nand have more text as needed.\n\n"))))
+    (describe "setting the comment"
+      (it "sets the comment when there isn't one yet."
+        (with-temp-vtt-buffer
+         (insert mock-vtt-comments-data)
+         (re-search-backward "This is a test")
+         (subed-set-subtitle-comment "Skip")
+         (expect (buffer-string) :to-equal
+                 "WEBVTT
+
+NOTE Skip
+
+00:00:00.000 --> 00:00:01.000
+This is a test.
+
+NOTE A comment can go here
+and have more text as needed.
+
+00:01:00.000 --> 00:00:02.000
+This is another test here.
+")))
+      (it "replaces the comment."
+        (with-temp-vtt-buffer
+         (insert mock-vtt-comments-data)
+         (goto-char (point-max))
+         (subed-set-subtitle-comment "Replaced.")
+         (expect (buffer-string) :to-equal
+                 "WEBVTT
+
+00:00:00.000 --> 00:00:01.000
+This is a test.
 
+NOTE Replaced.
+
+00:01:00.000 --> 00:00:02.000
+This is another test here.
+")))
+      (it "clears the comment."
+        (with-temp-vtt-buffer
+         (insert mock-vtt-comments-data)
+         (goto-char (point-max))
+         (subed-set-subtitle-comment nil)
+         (expect (buffer-string) :to-equal
+                 "WEBVTT
+
+00:00:00.000 --> 00:00:01.000
+This is a test.
+
+00:01:00.000 --> 00:00:02.000
+This is another test here.
+"))))
+    (describe "going to the next subtitle's comment"
+      (it "returns nil in an empty buffer."
+        (with-temp-vtt-buffer
+         (expect (subed-forward-subtitle-comment) :to-be nil)))
+      (it "returns nil at the end of the file."
+        (with-temp-vtt-buffer
+         (insert mock-vtt-comments-data)
+         (expect (subed-forward-subtitle-comment) :to-be nil)))
+      (it "returns nil if the next subtitle does not have a comment."
+        (with-temp-vtt-buffer
+         (insert mock-vtt-comments-data)
+         (save-excursion (subed-append-subtitle))
+         (expect (subed-forward-subtitle-comment) :to-be nil)))
+      (it "jumps to the next subtitle's comment."
+        (with-temp-vtt-buffer
+         (insert mock-vtt-comments-data)
+         (re-search-backward "This is a test")
+         (expect (subed-forward-subtitle-comment) :not :to-be nil)
+         (expect (looking-at "NOTE ") :to-be t))))
+    (describe "going to the previous comment"
+      (it "returns nil in an empty buffer."
+        (with-temp-vtt-buffer
+         (expect (subed-backward-subtitle-comment) :to-be nil)))
+      (it "returns nil at the start of the file."
+        (with-temp-vtt-buffer
+         (insert mock-vtt-comments-data)
+         (goto-char (point-min))
+         (expect (subed-backward-subtitle-comment) :to-be nil)))
+      (it "returns nil if the previous subtitle does not have a comment."
+        (with-temp-vtt-buffer
+         (insert mock-vtt-comments-data)
+         (re-search-backward "This is another test here")
+         (expect (subed-backward-subtitle-comment) :to-be nil)))
+      (it "jumps to the previous subtitle's comment."
+        (with-temp-vtt-buffer
+         (insert mock-vtt-comments-data)
+         (subed-append-subtitle)
+         (expect (subed-backward-subtitle-comment) :not :to-be nil)
+         (expect (looking-at "NOTE ") :to-be t)))))
   (describe "Merging with next subtitle"
     (it "throws an error in an empty buffer."
       (with-temp-vtt-buffer

Reply via email to