From 0a89bdf3fd1c2d1ada786138a4c433ba6b72dd7c Mon Sep 17 00:00:00 2001 From: taku0 Date: Tue, 21 Mar 2023 14:53:44 +0900 Subject: [PATCH] Update filling functionality. `fill-paragraph`, `fill-region`, and `auto-fill-mode` parse comments and multline strings as KDoc markdown and fill paragraphs with appropriate prefix (combination of `/`, `*`, `>`, and indentation). Example: ```kotlin /** * * * >> - >10. 2) aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa * >> > aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa * >> > aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa * * @param foo aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa * aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa * aaa aaa aaa * @param bar * aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa * aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa * aaa */ ``` --- kotlin-mode-fill.el | 716 +++++ kotlin-mode-kdoc-comment-parser.el | 872 ++++++ kotlin-mode-lexer.el | 31 + kotlin-mode.el | 20 +- scripts/run_linter.sh | 2 +- scripts/run_linter_in_docker.sh | 5 +- scripts/run_test_in_docker.sh | 2 +- test/commonmark_cases.json | 2620 ++++++++++++++++++ test/fill/code.kt | 5 + test/fill/comment.kt | 153 + test/kotlin-mode-fill-test.el | 638 +++++ test/kotlin-mode-kdoc-comment-parser-test.el | 118 + test/kotlin-mode-test.el | 46 +- 13 files changed, 5195 insertions(+), 33 deletions(-) create mode 100644 kotlin-mode-fill.el create mode 100644 kotlin-mode-kdoc-comment-parser.el create mode 100644 test/commonmark_cases.json create mode 100644 test/fill/code.kt create mode 100644 test/fill/comment.kt create mode 100644 test/kotlin-mode-fill-test.el create mode 100644 test/kotlin-mode-kdoc-comment-parser-test.el diff --git a/kotlin-mode-fill.el b/kotlin-mode-fill.el new file mode 100644 index 0000000..2ee38cd --- /dev/null +++ b/kotlin-mode-fill.el @@ -0,0 +1,716 @@ +;;; kotlin-mode-fill.el --- Major-mode for Kotlin, paragraph filling. -*- lexical-binding: t -*- + +;; Copyright (C) 2022 Josh Caswell, taku0 + +;; Author: Josh Caswell (https://github.com/woolsweater) +;; taku0 (http://github.com/taku0) + +;; This file is not part of GNU Emacs. + +;; This program 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 program 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 program. If not, see . + +;;; Commentary: + +;; Routines for paragraph filling + +;; Fill only paragraphs in comments and some multiline strings. +;; +;; If a line conains both code and comment/string, it is not filled. +;; +;; `auto-fill-mode' is intentionally disabled in multiline strings. +;; +;; `fill-region' is effective if the region is included in a string, but not if +;; it is partially out of the string, so you can apply `fill-region' to the +;; entire buffer to fill paragraphs in comments. +;; +;; Contents in comments/strings are parses as KDoc. Markdown paragraphs are +;; filled but other things like headers or block codes are not filled. + +;;; Code: + +(require 'rx) +(require 'kotlin-mode-lexer) +(require 'kotlin-mode-indent) +(require 'kotlin-mode-kdoc-comment-parser) + +(defun kotlin-mode--find-single-line-comment-edges () + "Return start and end of a single-line comment block. + +A single-line comment block is a continuous lines with same \"comment level\". + +Comment level is number of slashes after indentation. + +Return tuple (START . END) where START is the point before the first slash of +the block, and END is the end of the last comment, after the last line break if +any. + +Point may be anywhere in a single-line comment when this is called." + (let* ((current-comment-level (kotlin-mode--single-line-comment-level)) + (start + (save-excursion + (while (and (not (bobp)) + (= (kotlin-mode--single-line-comment-level) + current-comment-level)) + (forward-line -1)) + (unless (= (kotlin-mode--single-line-comment-level) + current-comment-level) + (forward-line 1)) + (back-to-indentation) + (point))) + (end + (save-excursion + (while (and (not (eobp)) + (= (kotlin-mode--single-line-comment-level) + current-comment-level)) + (when (/= (forward-line 1) 0) + (goto-char (point-max)))) + (point)))) + (cons start end))) +(defun kotlin-mode--single-line-comment-level () + "Return comment level of the current line. + +Return 1.0e+INF if the line doesn't start with a single-line comment." + (save-excursion + (save-match-data + (back-to-indentation) + (if (looking-at "//+") + (- (match-end 0) (match-beginning 0)) + ;; Not a comment + 1.0e+INF)))) + +(defun kotlin-mode--kdoc-comment-region (chunk) + "Return region of KDoc containing CHUNK. + +If CHUNK is a multiline comment or multiline string, return its region. + +If CHUNK is a single-line comment, see +`kotlin-mode--find-single-line-comment-edges' for the return value." + (cond + ((or (kotlin-mode--chunk-multiline-comment-p chunk) + (kotlin-mode--chunk-multiline-string-p chunk)) + (cons (kotlin-mode--chunk-start chunk) + (kotlin-mode--chunk-end chunk))) + ((kotlin-mode--chunk-single-line-comment-p chunk) + (save-excursion + (goto-char (kotlin-mode--chunk-start chunk)) + (kotlin-mode--find-single-line-comment-edges))) + (t nil))) + +(defun kotlin-mode--adaptive-fill () + "Return adaptive fill prefix for the current line." + (let* ((chunk (kotlin-mode--chunk-after-spaces)) + (comment-region (kotlin-mode--kdoc-comment-region chunk)) + (start (car comment-region)) + (end (cdr comment-region)) + (kdoc + ;; FIXME cache + (cond + ((kotlin-mode--chunk-multiline-comment-p chunk) + (kotlin-mode--parse-kdoc-in-multiline-comment start end)) + ((kotlin-mode--chunk-single-line-comment-p chunk) + (kotlin-mode--parse-kdoc-in-single-line-comments start end)) + ((kotlin-mode--chunk-multiline-string-p chunk) + (kotlin-mode--parse-kdoc-in-multiline-string start end)) + (t nil))) + (prefixes '()) + (current-column 0) + (ancestors '()) + tip + node + node-type) + (if (null kdoc) + (save-excursion + (back-to-indentation) + (buffer-substring-no-properties (line-beginning-position) (point))) + (save-excursion + (end-of-line) + (kotlin-mode--comment-node-walk + kdoc + (lambda (node event) + (when (and (eq event 'visit) + (not (gethash :first-child node))) + (cond + ((null tip) + (setq tip node)) + ((<= (gethash :start node) (point)) + (setq tip node)))))) + (setq node tip) + (while node + (push node ancestors) + (setq node (gethash :parent node))) + (while ancestors + (setq node (pop ancestors)) + (setq node-type (gethash :type node)) + (goto-char (gethash :start node)) + (cond + ((eq node-type 'single-line-comment) + (goto-char start) + ;; FIXME indent-tabs-mode + (push (make-string (current-column) ?\s) prefixes) + (push (buffer-substring-no-properties + (point) + (progn + (skip-chars-forward "/") + (point))) + prefixes) + (setq current-column (current-column)) + (when (memq (char-after) '(?\s ?\t ?\n nil)) + (push " " prefixes) + (setq current-column (1+ current-column)))) + ((eq node-type 'multiline-comment) + (goto-char start) + (forward-char) + ;; FIXME indent-tabs-mode + (push (make-string (current-column) ?\s) prefixes) + (setq current-column (current-column)) + (when kotlin-mode-prepend-asterisk-to-comment-line + (push "*" prefixes) + (skip-chars-forward "*") + (setq current-column (1+ current-column)) + (when (and kotlin-mode-insert-space-after-asterisk-in-comment + (memq (char-after) '(?\s ?\t ?\n nil))) + (push " " prefixes) + (setq current-column (1+ current-column))))) + ((eq node-type 'block-quote) + (skip-chars-forward "\s\t") + (push (make-string (- (current-column) current-column) ?\s) + prefixes) + (push ">" prefixes) + (forward-char) + (setq current-column (current-column)) + (when (memq (char-after) '(?\s ?\t ?\n nil)) + (push " " prefixes) + (setq current-column (1+ current-column)))) + ((eq node-type 'comment-tag) + (skip-chars-forward "\s\t") + (push (make-string (+ (- (current-column) current-column) + kotlin-mode-multiline-statement-offset) + ?\s) + prefixes) + (setq current-column (+ (current-column) + kotlin-mode-multiline-statement-offset))) + (t + (push (make-string (gethash :content-offset node) ?\s) prefixes) + (setq current-column + (+ current-column (gethash :content-offset node)))))) + (apply #'concat (reverse prefixes)))))) + +(defun kotlin-mode--fill-paragraph (justify) + "Fill paragraph in Kotlin code. + +JUSTIFY is as the argument of the same name in `fill-region'." + ;; TODO Handle trailing comments. + (save-excursion + (forward-line 0) + (save-match-data + (skip-syntax-backward " ") + (let* ((paragraphs-around (kotlin-mode--kdoc-paragraphs-around-point)) + (tip (nth 0 paragraphs-around)) + (next-leaf (nth 2 paragraphs-around)) + target-paragraph) + (when paragraphs-around + (setq target-paragraph (or tip next-leaf)) + (when target-paragraph + (fill-region-as-paragraph + (gethash :start target-paragraph) + (gethash :end target-paragraph) + justify)))) + t))) + +(defun kotlin-mode--fill-region-as-paragraph-advice + (fill-region-as-paragraph from to &rest args) + "Advice function for `fill-region-as-paragraph'. + +FILL-REGION-AS-PARAGRAPH is the original function, and FROM, TO, and ARGS are +original arguments. + +Fix up multiline comments. + +- When the region contains other than one multline comment, fill normally: + + foo() /* abc def ghi */ + ↓ + foo() /* abc + def ghi */ + +- Otherwise and when the region fits one line, fill as a line: + + /* + * abc + * def + */ + ↓ + /* abc def */ + +- Otherwise and when the region was one line, insert breaks before and after + the contents: + + /* abc def ghi */ + ↓ + /* + * abc def + * ghi + */ + +- Otherwise, keep line breaks around the contents and fill the contents: + + /* abc def ghi + */ + ↓ + /* abc def + * ghi + */" + (if (eq major-mode 'kotlin-mode) + (let ((chunk (save-excursion + (save-match-data + (goto-char from) + (kotlin-mode--chunk-after-spaces)))) + filling-entire-comment + from-without-spaces + to-without-spaces + ;; Position of comment including delimiters. + comment-start-pos + comment-end-pos + ;; Position of comment delimiters. + comment-opener-end + comment-closer-start + ;; Position of contents (without comment delimiters and spaces) + contents-start + contents-end + ;; Was the comment one line before filling? + was-one-line + ;; Is the contents (without delimiters) one line after filling? + is-one-line + ;; Are the contents and delimiters fits in one line? + fits-one-line + result) + (if (kotlin-mode--chunk-multiline-comment-p chunk) + (progn + (setq comment-start-pos (kotlin-mode--chunk-start chunk)) + (setq comment-end-pos (kotlin-mode--chunk-end chunk)) + (setq from-without-spaces + (save-excursion + (goto-char from) + (skip-syntax-forward " ") + (when (eq (char-after) ?\n) + (forward-char) + (skip-syntax-forward " ") + (when (and kotlin-mode-prepend-asterisk-to-comment-line + (eq (char-after) ?*)) + (forward-char) + (skip-syntax-forward " "))) + (skip-syntax-forward " >") + (point))) + (setq to-without-spaces + (save-excursion + (goto-char to) + (skip-syntax-backward " >") + (point))) + (setq contents-start + (save-excursion + (goto-char comment-start-pos) + (forward-char) + (skip-chars-forward "*") + (skip-syntax-forward " ") + (when (eq (char-after) ?\n) + (forward-char) + (skip-syntax-forward " ") + (when (and kotlin-mode-prepend-asterisk-to-comment-line + (eq (char-after) ?*)) + (forward-char) + (skip-syntax-forward " "))) + (skip-syntax-forward " >") + (point))) + (setq contents-end + (save-excursion + (goto-char comment-end-pos) + ;; Comment can be incomplete. + ;; Even worse, it can be "/*/". + (when (and (<= (+ comment-start-pos 2) + (- comment-end-pos 2)) + (eq (char-before) ?/) + (eq (char-before (1- (point))) ?*)) + (backward-char) + (skip-chars-backward "*") + (skip-syntax-backward " >")) + (point))) + (setq filling-entire-comment + (and (member from-without-spaces + (list comment-start-pos contents-start)) + (member to-without-spaces + (list comment-end-pos contents-end)))) + (if filling-entire-comment + (progn + (setq comment-end-pos (copy-marker comment-end-pos)) + (setq contents-end (copy-marker contents-end)) + (setq was-one-line (kotlin-mode--same-line-p + comment-start-pos + comment-end-pos)) + (setq result (apply + fill-region-as-paragraph + contents-start + contents-end + args)) + (setq is-one-line (kotlin-mode--same-line-p contents-start + contents-end)) + (cond + ((and was-one-line (not is-one-line)) + (save-excursion + (goto-char contents-end) + (insert-and-inherit ?\n) + (indent-according-to-mode) + (goto-char contents-start) + (insert-and-inherit "* ") + (goto-char contents-start) + (delete-horizontal-space) + (insert-and-inherit ?\n) + (indent-according-to-mode))) + ((and (not was-one-line) is-one-line) + (setq comment-opener-end + (save-excursion + (goto-char comment-start-pos) + (forward-char) + (skip-chars-forward "*") + (point-marker))) + (setq comment-closer-start + (save-excursion + (goto-char contents-end) + (skip-syntax-forward " >") + (copy-marker (min (point) comment-end-pos)))) + (setq contents-start (copy-marker contents-start)) + ;; Determinate whether the contents and the delimiter + ;; fits one line. + ;; Using `transpose-regions' rather than + ;; `delete-and-extract-region' to keep markers. + ;; + ;; `transpose-regions' moves markers as if the markers + ;; were placed after their position: + ;; + ;; /**1 + ;; * 2abc def3 + ;; 4*/5 + ;; + ;; ↓ (transpose-regions m1 m1 m2 m3) + ;; + ;; /**2abc def1 + ;; * 3 + ;; 4*/5 + (transpose-regions comment-opener-end + comment-opener-end + contents-start + contents-end) + ;; /**2abc def1 + ;; * 3 + ;; 4*/5 + ;; + ;; ↓ (transpose-regions m1 m1 m4 m5) + ;; + ;; /**2abc def4*/1 + ;; * 3 + ;; 5 + (transpose-regions comment-opener-end + comment-opener-end + comment-closer-start + comment-end-pos) + (save-excursion + (goto-char comment-closer-start) + (insert-and-inherit ?\s) + (goto-char contents-start) + (insert-and-inherit ?\s) + (goto-char comment-opener-end) + (setq fits-one-line (<= (current-column) fill-column)) + (goto-char contents-start) + (delete-char 1) + (goto-char comment-closer-start) + (delete-char 1)) + ;; /**2abc def4*/1 + ;; * 3 + ;; 5 + ;; + ;; ↓ (transpose-regions m4 m1 m5 m5) + ;; + ;; /**2abc def1 + ;; * 3 + ;; 4*/5 + (transpose-regions comment-closer-start + comment-opener-end + comment-end-pos + comment-end-pos) + ;; /**2abc def1 + ;; * 3 + ;; 4*/5 + ;; + ;; ↓ (transpose-regions m2 m1 m3 m3) + ;; + ;; /**1 + ;; * 2abc def3 + ;; 4*/5 + (transpose-regions contents-start + comment-opener-end + contents-end + contents-end) + (when fits-one-line + (save-excursion + (delete-region comment-opener-end contents-start) + (goto-char comment-opener-end) + (insert-and-inherit ?\s) + (delete-region contents-end comment-closer-start) + (goto-char contents-end) + (insert-and-inherit ?\s))) + (set-marker contents-start nil) + (set-marker comment-closer-start nil) + (set-marker comment-opener-end nil))) + (set-marker contents-end nil) + (set-marker comment-end-pos nil) + result) + (apply fill-region-as-paragraph from to args))) + (apply fill-region-as-paragraph from to args))) + (apply fill-region-as-paragraph from to args))) + +(defun kotlin-mode--install-fill-region-as-paragraph-advice () + "Install advice around `fill-region-as-paragraph'." + (advice-add 'fill-region-as-paragraph + :around + #'kotlin-mode--fill-region-as-paragraph-advice)) + +(defun kotlin-mode--fill-forward-paragraph (arg) + "Forward ARG paragraphs for filling. + +Returns the count of paragraphs left to move." + (if (< arg 0) + (kotlin-mode--fill-backward-paragraph (- arg)) + (let ((done nil)) + (while (and (< 0 arg) + (not done)) + (setq done (not (kotlin-mode--fill-skip-paragraph-1 'forward))) + (unless done (setq arg (1- arg))))) + arg)) + +(defun kotlin-mode--fill-backward-paragraph (arg) + "Backward ARG paragraphs for filling. + +Returns the count of paragraphs left to move." + (if (< arg 0) + (kotlin-mode--fill-forward-paragraph (- arg)) + (let ((done nil)) + (while (and (< 0 arg) + (not done)) + (setq done (not (kotlin-mode--fill-skip-paragraph-1 'backward))) + (unless done (setq arg (1- arg))))) + arg)) + +(defun kotlin-mode--fill-skip-paragraph-1 (direction) + "Skip a paragraph for filling. + +If DIRECTION is `backward', skip backward. Otherwise, skip forward. + +Return non-nil if skipped a paragraph. Return nil otherwise." + (save-match-data + ;; Skip whitespaces. + (if (eq direction 'backward) + (skip-syntax-backward " ") + (skip-syntax-forward " ")) + (let* ((chunk (kotlin-mode--chunk-after-spaces)) + (comment-region (kotlin-mode--kdoc-comment-region chunk)) + (start (car comment-region)) + (end (cdr comment-region)) + (paragraphs-around (kotlin-mode--kdoc-paragraphs-around-point)) + (tip (nth 0 paragraphs-around)) + (previous-leaf (nth 1 paragraphs-around)) + (next-leaf (nth 2 paragraphs-around)) + target-paragraph) + (if (null paragraphs-around) + (kotlin-mode--fill-skip-paragraph-in-code direction) + (setq target-paragraph + (cond + ;; Point is between paragraphs. + ((null tip) + (if (eq direction 'backward) + previous-leaf + next-leaf)) + + ;; Point is at the start of a paragraph. + ((eq (gethash :start tip) (point)) + (if (eq direction 'backward) + previous-leaf + tip)) + + ;; Point is at the end of a paragraph. + ((eq (gethash :end tip) (point)) + (if (eq direction 'backward) + tip + next-leaf)) + + ;; Point is at the middle of a paragraph. + (t tip))) + (if (eq direction 'backward) + (if target-paragraph + (progn + (goto-char (gethash :start target-paragraph)) + t) + (goto-char start) + (kotlin-mode--fill-skip-paragraph-in-code direction)) + (if target-paragraph + (progn + (goto-char (gethash :end target-paragraph)) + (when (eq (char-after) ?\n) + (forward-char)) + (skip-chars-backward "\s\t") + t) + (goto-char end) + (kotlin-mode--fill-skip-paragraph-in-code direction))))))) + +(defun kotlin-mode--kdoc-paragraphs-around-point () + "Return KDoc paragraphs around the point. + +If no KDoc is under the point, return nil. + +Otherwise, return a list (TIP PREVIOUS NEXT) where TIP is the paragraph under +the point if any, PREVIOUS is the preceding paragraph if any, and the NEXT is + the following paragraph if any." + (let* ((chunk (kotlin-mode--chunk-after-spaces)) + (comment-region (kotlin-mode--kdoc-comment-region chunk)) + (start (car comment-region)) + (end (cdr comment-region)) + (kdoc + ;; FIXME cache + (cond + ((kotlin-mode--chunk-multiline-comment-p chunk) + (kotlin-mode--parse-kdoc-in-multiline-comment start end)) + ((kotlin-mode--chunk-single-line-comment-p chunk) + (kotlin-mode--parse-kdoc-in-single-line-comments start end)) + ((kotlin-mode--chunk-multiline-string-p chunk) + (kotlin-mode--parse-kdoc-in-multiline-string start end)) + (t nil))) + tip + next-leaf + previous-leaf) + (if (null kdoc) + nil + (kotlin-mode--comment-node-walk + kdoc + (lambda (node event) + (when (and (eq event 'visit) + (eq (gethash :type node) 'paragraph)) + (when (<= (gethash :end node) (point)) + (setq previous-leaf node)) + (when (and (<= (gethash :start node) (point)) + (< (point) (gethash :end node))) + (setq tip node)) + (when (and (< (point) (gethash :start node)) + (null next-leaf)) + (setq next-leaf node))))) + (list tip previous-leaf next-leaf)))) + +(defun kotlin-mode--fill-skip-paragraph-in-code (direction) + "Find next paragraph to fill. + +If DIRECTION is `backward', skip backward. Otherwise, skip forward. + +Search a line that is a part of a comment, then search a paragraph in it. + +Return non-nil if skipped a paragraph. Return nil otherwise." + (let (chunk) + (if (eq direction 'backward) + (progn + (while (and + (zerop (forward-line -1)) + (progn + (setq chunk (kotlin-mode--chunk-after-spaces)) + (unless (kotlin-mode--chunk-comment-p chunk) + (setq chunk nil)) + (null chunk))) + t) + (when chunk + (goto-char (kotlin-mode--chunk-end chunk)) + (cond + ((kotlin-mode--chunk-multiline-comment-p chunk) + (backward-char 2)) + ((kotlin-mode--chunk-single-line-comment-p chunk) + (backward-char))) + (kotlin-mode--fill-skip-paragraph-1 direction))) + (while (and + (not (eobp)) + (progn + (setq chunk (kotlin-mode--chunk-after-spaces)) + (unless (kotlin-mode--chunk-comment-p chunk) + (setq chunk nil)) + (null chunk))) + (forward-line 1)) + (when chunk + (goto-char (kotlin-mode--chunk-start chunk)) + (kotlin-mode--fill-skip-paragraph-1 direction))))) + +(defun kotlin-mode--do-auto-fill () + "Do auto fill at point. + +Do nothing except in a comment or multiline. + +If point is inside a multiline style comment (slash-star style comment) which +is actually in single line, insert line breaks before and after the contents, +then call `do-auto-fill'. + +Example: + +/* aaa bbb ccc */ +↓ +/* + * aaa bbb + * ccc + */" + (let ((current-fill-column (current-fill-column)) + (current-justification (current-justification)) + chunk + comment-start-pos + comment-end-pos + fill-prefix) + (when (and current-justification + fill-column + (not (and (eq current-justification 'left) + (<= (current-column) current-fill-column))) + (setq chunk (kotlin-mode--chunk-after)) + (kotlin-mode--chunk-comment-p chunk)) + (when (kotlin-mode--chunk-multiline-comment-p chunk) + (setq comment-start-pos (kotlin-mode--chunk-start chunk)) + (setq comment-end-pos (kotlin-mode--chunk-end chunk)) + (when (kotlin-mode--same-line-p comment-start-pos comment-end-pos) + (save-excursion + (goto-char comment-end-pos) + (when (and (<= (+ comment-start-pos 2) + (- comment-end-pos 2)) + (eq (char-before) ?/) + (eq (char-before (1- (point))) ?*)) + (backward-char) + (skip-chars-backward "*") + (skip-syntax-backward " ") + (delete-horizontal-space) + (insert-and-inherit ?\n)) + (indent-according-to-mode)) + (save-excursion + (goto-char comment-start-pos) + (forward-char) + (skip-chars-forward "*") + (delete-horizontal-space) + (insert-and-inherit ?\n) + (when kotlin-mode-prepend-asterisk-to-comment-line + (insert ?*) + (when kotlin-mode-insert-space-after-asterisk-in-comment + (insert-and-inherit ?\s))) + (indent-according-to-mode)))) + (setq fill-prefix (kotlin-mode--adaptive-fill)) + (do-auto-fill)))) + +(provide 'kotlin-mode-fill) + +;;; kotlin-mode-fill.el ends here diff --git a/kotlin-mode-kdoc-comment-parser.el b/kotlin-mode-kdoc-comment-parser.el new file mode 100644 index 0000000..72dc765 --- /dev/null +++ b/kotlin-mode-kdoc-comment-parser.el @@ -0,0 +1,872 @@ +;;; kotlin-mode-kdoc-comment-parser.el --- Major-mode for Kotlin, KDoc comment parser for filling. -*- lexical-binding: t -*- + +;; Copyright (C) 2023 taku0 + +;; Author: taku0 (http://github.com/taku0) + +;; This file is not part of GNU Emacs. + +;; This program 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 program 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 program. If not, see . + +;;; Commentary: + +;; Routines for parsing KDoc comment. Only block level parser is implemented. + +;;; Code: + +(require 'rx) +(require 'cl-lib) + +(cl-defun kotlin-mode--comment-node + (&key + type + start + end + content-offset + closed + next + previous + parent + first-child + last-child + (spans (list)) + code-fence + list-marker-type) + "Construct and return a comment node as a hash table. + +TYPE: the type of the node. + Valid types: + + - document + - thematic-break + - atx-heading + - setext-heading + - indented-code-block + - fenced-code-block + - paragraph + - block-quote + - list-item + - list + - single-line-comment (slashes at start of each lines comments) + - multiline-comment (asterisk at start of each lines of comments) + - comment-tag (at-tag of KDoc) + + HTML block nor link reference definitions are not implemented. + +START: the start position. + +END: the end position. + +CONTENT-OFFSET: the start column of the content relative to parent. + +CLOSED: the node is closed or not. + +NEXT: the following sibling if any. + +PREVIOUS: the preceding sibling if any. + +PARENT: the parent if any. + +FIRST-CHILD: the first child if any. + +LAST-CHILD: the last child if any. + +SPANS: the text spans. A list of tuple (START START-COLUMN END) where START is + the start position, START-COLUMN is the start column, that can be middle of a + tab character, and END is the end position. Reversed until finished. + +CODE-FENCE: the marker of fence for fenced code block. + +LIST-MARKER-TYPE: the type of list marker for list item. One of symbols `-', + `+', `*', `number-dot', or `number-parenthesis'." + (let ((table (make-hash-table))) + (puthash :type type table) + (puthash :start start table) + (puthash :end end table) + (puthash :content-offset content-offset table) + (puthash :closed closed table) + (puthash :next next table) + (puthash :previous previous table) + (puthash :parent parent table) + (puthash :first-child first-child table) + (puthash :last-child last-child table) + (puthash :spans spans table) + (puthash :code-fence code-fence table) + (puthash :list-marker-type list-marker-type table) + table)) + +(defun kotlin-mode--comment-node-dump (node) + "Return string representation of the NODE. + +Intented for debugging and testing." + (let ((indent "") + (chunks '())) + (kotlin-mode--comment-node-walk + node + (lambda (node event) + (cond + ((eq event 'visit) + (when chunks + (push "\n" chunks)) + (push (format + "%s%s %d-%d" + indent + (gethash :type node) + (line-number-at-pos (gethash :start node)) + ;; (save-excursion + ;; (goto-char (gethash :start node)) + ;; (current-column)) + (line-number-at-pos (gethash :end node)) + ;; (save-excursion + ;; (goto-char (gethash :end node)) + ;; (current-column)) + ) + chunks)) + ((eq event 'enter) + (setq indent (concat indent " "))) + ((eq event 'leave) + (setq indent (substring indent 0 (- (length indent) 2))))))) + (apply #'concat (reverse chunks)))) + +(defun kotlin-mode--comment-node-walk (root handler) + "Traverse tree from ROOT in depth first order. + +Call HANDLER with two arguments NODE EVENT, where NODE is the visiting node and +EVENT is one of the symbols below: + +- `visit': visiting the NODE. +- `enter': going to visit NODE's children. +- `leave': finished to visit NODE's children. + +Don't consume stack even with deep tree." + (let ((node root) + (done nil)) + (while (not done) + (funcall handler node 'visit) + (cond + ((gethash :first-child node) + (funcall handler node 'enter) + (setq node (gethash :first-child node))) + ((gethash :next node) + (setq node (gethash :next node))) + (t + (while (and (not (gethash :next node)) + (not (eq node root))) + (setq node (gethash :parent node)) + (unless (eq node root) + (funcall handler node 'leave))) + (if (eq node root) + (setq done t) + (setq node (gethash :next node)))))))) + +(defun kotlin-mode--comment-node-add-child (parent child) + "Add a CHILD node to the given PARENT node. + +If the PARENT is a list and the CHILD is not a list item, finalize the PARENT +and add the CHILD to the grandparent." + (when (and (eq (gethash :type parent) 'list) + (not (eq (gethash :type child) 'list-item))) + (kotlin-mode--comment-node-finalize parent t) + (setq parent (gethash :parent parent))) + (setf (gethash :parent child) parent) + (let ((last-child (gethash :last-child parent))) + (if (null last-child) + (progn + (setf (gethash :first-child parent) child) + (setf (gethash :last-child parent) child)) + (setf (gethash :next last-child) child) + (setf (gethash :previous child) last-child) + (setf (gethash :last-child parent) child)))) + +(defun kotlin-mode--comment-node-close-descendants (node close-at-previous-line) + "Close descendants of the NODE. + +Set the end position of the NODE to the end of the current line if +CLOSE-AT-PREVIOUS-LINE is nil. Otherwise, set it to the end of the previous +line." + (let ((tip (kotlin-mode--comment-node-last-node node))) + (while (and tip (not (eq tip node))) + (kotlin-mode--comment-node-finalize tip close-at-previous-line) + (setq tip (gethash :parent tip))))) + +(defun kotlin-mode--comment-node-trim-end (node) + "Remove trailing blank lines from spans of the NODE." + (let ((spans (reverse (gethash :spans node))) + (end (gethash :end node))) + (while (and spans + (save-excursion + (goto-char (nth 0 (car spans))) + (skip-chars-forward "\s\t") + (<= (nth 2 (car spans)) (point)))) + (setq end (nth 2 (car spans))) + (pop spans)) + (when spans + (setq end (nth 2 (car spans)))) + (setf (gethash :end node) end) + (setf (gethash :spans node) (reverse spans)))) + +(defun kotlin-mode--comment-node-finalize (node close-at-previous-line) + "Close and set the end position of the NODE. + +Set the end position of the NODE to the end of the current line if +CLOSE-AT-PREVIOUS-LINE is nil. Otherwise, set it to the end of the previous +line. + +The end position is adjusted according to the type of the NODE." + (unless (gethash :closed node) + (setf (gethash :closed node) t) + (setf (gethash :spans node) (reverse (gethash :spans node))) + (let ((end + (if close-at-previous-line + (save-excursion + (forward-line 0) + (unless (bobp) (backward-char)) + (point)) + (line-end-position)))) + (setf (gethash :end node) end)) + (cond + ;; Indented code block + ((eq (gethash :type node) 'indented-code-block) + ;; Indented code blocks don't contain trailing blank lines while fenced + ;; code blocks do, even if it is closed implicitly: + ;; + ;; - ``` + ;; The following line is a part of the code block: + ;; + ;; - This line is not a part of the code block. + ;; It has been closed implicitly. + (kotlin-mode--comment-node-trim-end node)) + + ;; List item + ((eq (gethash :type node) 'list-item) + (if (gethash :last-child node) + (setf (gethash :end node) + (gethash :end (gethash :last-child node))) + (save-excursion + (goto-char (gethash :start node)) + (setf (gethash :end node) (line-end-position))))) + + ;; List + ((eq (gethash :type node) 'list) + (setf (gethash :end node) + (gethash :end (gethash :last-child node)))) + + ;; Comment tag + ((eq (gethash :type node) 'comment-tag) + (if (gethash :last-child node) + (setf (gethash :end node) + (gethash :end (gethash :last-child node))) + (save-excursion + (goto-char (gethash :start node)) + (setf (gethash :end node) (line-end-position)))))))) + +(defun kotlin-mode--comment-node-last-node (node) + "Return the last leaf of the NODE." + (let ((tip node)) + (while (gethash :last-child tip) + (setq tip (gethash :last-child tip))) + tip)) + +(defun kotlin-mode--comment-node-can-continue-paragraph (node) + "Return non-nil if and only if the current line can continue paragraph. + +NODE is the partially parsed tree." + (let ((tip (kotlin-mode--comment-node-last-node node))) + (and (not (gethash :closed tip)) + (eq (gethash :type tip) 'paragraph)))) + +(defvar-local kotlin-mode--comment-parser-current-column 0 + "Current column of the comment parser, that can be the middle of a tab.") + +(defun kotlin-mode--comment-parser-forward-column (&optional arg) + "Forward the parser ARG columns." + (kotlin-mode--comment-parser-move-to-column + (+ kotlin-mode--comment-parser-current-column (or arg 1)))) + +(defun kotlin-mode--comment-parser-move-to-column (column) + "Move the parser to COLUMNth columns." + (setq kotlin-mode--comment-parser-current-column column) + (move-to-column column)) + +(defun kotlin-mode--comment-parser-end-of-line () + "Move the parser to the end of the line." + (end-of-line) + (setq kotlin-mode--comment-parser-current-column (current-column))) + +(defun kotlin-mode--comment-parser-add-span (parent) + "Add the rest of the current line to the spans of the PARENT node." + (push (list (point) + kotlin-mode--comment-parser-current-column + (line-end-position)) + (gethash :spans (kotlin-mode--comment-node-last-node parent))) + (kotlin-mode--comment-parser-end-of-line)) + +(defun kotlin-mode--comment-parser-try-start (parent) + "Try to start a new node and add to the PARENT. + +If a node is created, return the new container node, that can be the new node +or the PARENT. Otherwise, return nil. + +Close the descendants of the PARENT if needed." + (let ((indent (- (save-excursion + (skip-chars-forward "\s\t") + (current-column)) + kotlin-mode--comment-parser-current-column))) + (cond + ;; Content of code block + ((memq (gethash :type parent) '(indented-code-block fenced-code-block)) + (kotlin-mode--comment-parser-add-span parent) + (kotlin-mode--comment-parser-end-of-line) + nil) + + ;; Setext heading + ((and (< indent 4) + (looking-at (rx (seq (zero-or-more (any "\s\t")) + (or (one-or-more "=") + (>= 2 "-")) + (zero-or-more (any "\s\t")) + line-end))) + (let ((preceding (gethash :last-child parent))) + ;; FIXME it should not be link reference definitions. + (and + preceding + (eq (gethash :type preceding) 'paragraph) + (not (gethash :closed preceding))))) + (kotlin-mode--comment-node-close-descendants parent nil) + (setf (gethash :type (gethash :last-child parent)) 'setext-heading) + (kotlin-mode--comment-parser-end-of-line) + nil) + + ;; Thematic break + ((and (< indent 4) + (looking-at + (rx (seq (zero-or-more (any "\s\t")) + (or (>= 3 (seq "-" (zero-or-more (any "\s\t")))) + (>= 3 (seq "_" (zero-or-more (any "\s\t")))) + (>= 3 (seq "*" (zero-or-more (any "\s\t"))))) + (zero-or-one (any "\s\t")) + line-end)))) + (kotlin-mode--comment-node-close-descendants parent t) + (kotlin-mode--comment-node-add-child parent + (kotlin-mode--comment-node + :type 'thematic-break + :start (point) + :end (line-end-position) + :content-offset (line-end-position) + :closed t)) + (kotlin-mode--comment-parser-end-of-line) + nil) + + ;; List item + ((and (< indent 4) + (looking-at (rx (seq (zero-or-more (any "\s\t")) + (or (any "-+*") + (seq (** 1 9 (any "0-9")) + (any ".)"))) + (or (any "\s\t") + line-end)))) + ;; When the first list item in a list interrupts a paragraph—that + ;; is, when it starts on a line that would otherwise count as + ;; paragraph continuation text—then (a) the lines Ls must not begin + ;; with a blank line, and (b) if the list item is ordered, the start + ;; number must be 1. + ;; https://spec.commonmark.org/0.30/#list-items + (or (not + (and + (gethash :last-child parent) + (eq (gethash :type (gethash :last-child parent)) 'paragraph) + (kotlin-mode--comment-node-can-continue-paragraph parent))) + (looking-at (rx (seq (zero-or-more (any "\s\t")) + (or (any "-+*") + (seq "1" (any ".)"))) + (* (any "\s\t")) + (not (any "\s\t\n"))))))) + (kotlin-mode--comment-node-close-descendants parent t) + (let ((start (point)) + (start-column kotlin-mode--comment-parser-current-column) + content-offset + list + item + marker-type) + (skip-chars-forward "\s\t") + (skip-chars-forward "0-9") + (setq marker-type (cond ((eq (char-after) ?-) '-) + ((eq (char-after) ?+) '+) + ((eq (char-after) ?*) '*) + ((eq (char-after) ?.) 'number-dot) + (t 'number-parenthesis))) + (skip-chars-forward "-+*.)") + (setq kotlin-mode--comment-parser-current-column (current-column)) + (setq content-offset + (progn + (cond + ;; Starting with blank line. + ((looking-at (rx (seq (zero-or-more (any "\s\t")) + line-end))) + (kotlin-mode--comment-parser-forward-column)) + + ;; Starting with indented code. + ((save-excursion + (skip-chars-forward "\s\t") + (>= (- (current-column) + kotlin-mode--comment-parser-current-column) + 5)) + (kotlin-mode--comment-parser-forward-column)) + + ;; Otherwise. + (t + (skip-chars-forward "\s\t") + (setq kotlin-mode--comment-parser-current-column + (current-column)))) + (- kotlin-mode--comment-parser-current-column start-column))) + ;; Add list node if needed. + (if (eq (gethash :type parent) 'list) + (if (eq (gethash :list-marker-type (gethash :first-child parent)) + marker-type) + (setq list parent) + (kotlin-mode--comment-node-finalize parent t) + (setq list (kotlin-mode--comment-node + :type 'list + :start start + :content-offset 0)) + (kotlin-mode--comment-node-add-child (gethash :parent parent) + list)) + (setq list (kotlin-mode--comment-node + :type 'list + :start start + :content-offset 0)) + (kotlin-mode--comment-node-add-child parent list)) + ;; Add item. + (setq item (kotlin-mode--comment-node + :type 'list-item + :start start + :content-offset content-offset + :list-marker-type marker-type)) + (kotlin-mode--comment-node-add-child list item) + item)) + + ;; ATX heading + ((and (< indent 4) + (looking-at (rx (seq (zero-or-more (any "\s\t")) + (** 1 6 "#") + (or (any "\s\t") + line-end))))) + (kotlin-mode--comment-node-close-descendants parent t) + (let ((start (point)) + (start-column kotlin-mode--comment-parser-current-column)) + (skip-chars-forward "\s\t") + (skip-chars-forward "#") + (skip-chars-forward "\s\t") + (kotlin-mode--comment-node-add-child + parent + (kotlin-mode--comment-node + :type 'atx-heading + :start start + :end (line-end-position) + :content-offset (- (current-column) start-column) + :closed t))) + (kotlin-mode--comment-parser-end-of-line) + nil) + + ;; Indented code block + ((and (>= indent 4) + (looking-at (rx (seq (zero-or-more (any "\s\t")) + (not (any "\s\t\n"))))) + (not (kotlin-mode--comment-node-can-continue-paragraph parent))) + (kotlin-mode--comment-node-close-descendants parent t) + (let ((code-block + (kotlin-mode--comment-node + :type 'indented-code-block + :start (point) + :end (line-end-position) + :content-offset 4))) + (kotlin-mode--comment-node-add-child parent code-block) + (kotlin-mode--comment-parser-forward-column 4) + (kotlin-mode--comment-parser-add-span parent) + (kotlin-mode--comment-parser-end-of-line) + nil)) + + ;; Fenced code block + ((and (< indent 4) + (looking-at + (rx (seq (zero-or-more (any "\s\t")) + (or (seq (>= 3 "`") + (zero-or-more (not (any "`"))) + line-end) + (>= 3 "~")))))) + (kotlin-mode--comment-node-close-descendants parent t) + (let* ((start (point)) + (content-offset (progn + (skip-chars-forward "\s\t") + (- (current-column) + kotlin-mode--comment-parser-current-column))) + (code-fence (progn + (buffer-substring-no-properties + (point) + (progn + (skip-chars-forward (string (char-after))) + (point))))) + (code-block (kotlin-mode--comment-node + :type 'fenced-code-block + :start start + :content-offset content-offset + :code-fence code-fence))) + (kotlin-mode--comment-node-add-child parent code-block) + (kotlin-mode--comment-parser-end-of-line) + nil)) + + ;; Block quote + ((and (< indent 4) + (looking-at (rx (seq (zero-or-more (any "\s\t")) + ">")))) + (kotlin-mode--comment-node-close-descendants parent t) + (let ((start (point)) + block-quote) + (skip-chars-forward "\s\t") + (forward-char) + (setq kotlin-mode--comment-parser-current-column (current-column)) + (setq block-quote (kotlin-mode--comment-node + :type 'block-quote + :start start + :content-offset 2)) + (kotlin-mode--comment-node-add-child parent block-quote) + (when (memq (char-after) '(?\s ?\t)) + (kotlin-mode--comment-parser-forward-column)) + block-quote)) + + ;; Comment tag + ((and (< indent 4) + (let ((parent-type (gethash :type parent))) + (when (eq parent-type 'list) + (setq parent-type (gethash :type (gethash :parent parent)))) + (memq parent-type + '(document single-line-comment multiline-comment))) + (looking-at (rx (seq (zero-or-more (any "\s\t")) + "@" + (any "a-zA-Z"))))) + ;; Markdown parser of KDoc is different to CommonMark. + ;; We struggle to be consistent to CommonMark, but not be too strict. + (kotlin-mode--comment-node-close-descendants parent t) + (let* ((start (point)) + (tag-name (progn + (skip-chars-forward "\s\t") + (forward-char) + (looking-at (rx (zero-or-more (any "a-zA-Z0-9")))) + (match-string-no-properties 0))) + (content-offset + (progn + (skip-chars-forward "a-zA-Z0-9") + (when (member tag-name '("param" + "property" + "throws" + "exception")) + (skip-chars-forward "\s\t") + (skip-chars-forward "^\s\t\n")) + (setq kotlin-mode--comment-parser-current-column + (current-column)) + kotlin-mode-multiline-statement-offset)) + (comment-tag (kotlin-mode--comment-node + :type 'comment-tag + :start start + :content-offset content-offset))) + (kotlin-mode--comment-node-add-child parent comment-tag) + comment-tag)) + + ;; Paragraph + ((not (looking-at (rx (seq (zero-or-more (any "\s\t")) + line-end)))) + (unless (kotlin-mode--comment-node-can-continue-paragraph parent) + (kotlin-mode--comment-node-close-descendants parent t) + (kotlin-mode--comment-node-add-child parent + (kotlin-mode--comment-node + :type 'paragraph + :start (point) + :content-offset 0))) + (kotlin-mode--comment-parser-add-span parent) + (kotlin-mode--comment-parser-end-of-line) + nil) + + ;; Blank line + (t + (kotlin-mode--comment-node-close-descendants parent t) + nil)))) + +(defun kotlin-mode--comment-parser-check-continued (node) + "Check the current line is continuation of the NODE. + +If the current line is continuation of the NODE, advance parser to the content +column of the NODE. + +Otherwise, return nil and keep the point intact. + +If the current line explicitly close the NODE, finalize the NODE." + (let ((node-type (gethash :type node)) + (indent (- (save-excursion + (skip-chars-forward "\s\t") + (current-column)) + kotlin-mode--comment-parser-current-column)) + (blank (looking-at (rx (seq (zero-or-more (any "\s\t")) + line-end))))) + (cond + ;; Document + ((eq node-type 'document) + t) + + ;; Thematic break + ((eq node-type 'thematic-break) + nil) + + ;; ATX heading + ((eq node-type 'atx-heading) + nil) + + ;; Setext heading + ((eq node-type 'setext-heading) + nil) + + ;; Indented code block + ((eq node-type 'indented-code-block) + (when (or (>= indent 4) blank) + (kotlin-mode--comment-parser-forward-column 4) + t)) + + ;; Fenced code block + ((eq node-type 'fenced-code-block) + (let ((closed (and (< indent 4) + (looking-at (rx (seq (zero-or-more (any "\s\t")) + (or (one-or-more "`") + (one-or-more "~")) + (zero-or-more (any "\s\t")) + line-end))) + (save-excursion + (skip-chars-forward "\s\t") + (looking-at (gethash :code-fence node)))))) + (if closed + (progn + (kotlin-mode--comment-node-finalize node nil) + (kotlin-mode--comment-parser-end-of-line) + nil) + (kotlin-mode--comment-parser-move-to-column + (min (save-excursion + (skip-chars-forward "\s\t") + (current-column)) + (progn + (kotlin-mode--comment-parser-forward-column + (gethash :content-offset node)) + kotlin-mode--comment-parser-current-column))) + t))) + + ;; Paragraph + ((eq node-type 'paragraph) + (when blank + (progn + (kotlin-mode--comment-node-finalize node t) + (kotlin-mode--comment-parser-end-of-line))) + nil) + + ;; Block quote + ((eq node-type 'block-quote) + (when (and (< indent 4) + (looking-at (rx (seq (zero-or-more (any "\s\t")) + ">")))) + (skip-chars-forward "\s\t") + (forward-char) + (setq kotlin-mode--comment-parser-current-column (current-column)) + (when (memq (char-after) '(?\s ?\t)) + (kotlin-mode--comment-parser-forward-column)) + t)) + + ;; List item + ((eq node-type 'list-item) + (when (or (and + ;; List item can contain blank line. + blank + ;; List items cannot start with multiple blank lines. + (gethash :first-child node)) + (save-excursion + (skip-chars-forward "\s\t") + (<= (+ kotlin-mode--comment-parser-current-column + (gethash :content-offset node)) + (current-column)))) + (kotlin-mode--comment-parser-forward-column + (gethash :content-offset node)) + t)) + + ;; List + ((eq node-type 'list) + t) + + ;; Single line comment + ((eq node-type 'single-line-comment) + (skip-chars-forward "\s\t") + (skip-chars-forward "/") + (setq kotlin-mode--comment-parser-current-column (current-column)) + (when (memq (char-after) '(?\s ?\t)) + (kotlin-mode--comment-parser-forward-column)) + t) + + ;; Multiline comment + ((eq node-type 'multiline-comment) + (if (and kotlin-mode-prepend-asterisk-to-comment-line + (< indent (+ (gethash :content-offset node) 4)) + (looking-at (rx (seq (zero-or-more (any "\s\t")) + "*")))) + (progn + (skip-chars-forward "\s\t") + (forward-char) + (setq kotlin-mode--comment-parser-current-column (current-column)) + (when (and kotlin-mode-insert-space-after-asterisk-in-comment + (memq (char-after) '(?\s ?\t))) + (kotlin-mode--comment-parser-forward-column))) + (kotlin-mode--comment-parser-move-to-column + (min (save-excursion + (skip-chars-forward "\s\t") + (current-column)) + (progn + (kotlin-mode--comment-parser-forward-column + (gethash :content-offset node)) + kotlin-mode--comment-parser-current-column)))) + t) + + ;; Comment tag + ((eq node-type 'comment-tag) + (if (and (< indent 4) + (looking-at (rx (seq (zero-or-more (any "\s\t")) + "@" + (any "a-zA-Z"))))) + (progn + (kotlin-mode--comment-node-finalize node t) + nil) + (kotlin-mode--comment-parser-move-to-column + (min (save-excursion + (skip-chars-forward "\s\t") + (current-column)) + (progn + (kotlin-mode--comment-parser-forward-column + (gethash :content-offset node)) + kotlin-mode--comment-parser-current-column))) + t))))) + +(defun kotlin-mode--comment-parser-add-line (document) + "Add a line to the DOCUMENT." + (let ((node (gethash :last-child document)) + (parent document)) + (while node + (unless (and (not (gethash :closed node)) + (kotlin-mode--comment-parser-check-continued node)) + (setq node nil)) + (when node + (setq parent node) + (setq node (gethash :last-child node)))) + (while (setq parent (kotlin-mode--comment-parser-try-start parent)) + nil))) + +(defun kotlin-mode--parse-kdoc-in-single-line-comments (start end) + "Parse a block of single line comments as KDoc comment. + +The block is assumed to be from START to END." + (let ((document (kotlin-mode--comment-node + :type 'document + :start start + :content-offset 0))) + (kotlin-mode--comment-node-add-child + document + (kotlin-mode--comment-node + :type 'single-line-comment + :start start + :content-offset (save-excursion + (goto-char start) + (skip-chars-forward "\s\t") + (+ 3 (current-column))))) + (kotlin-mode--parse-kdoc-comment start end document))) + +(defun kotlin-mode--parse-kdoc-in-multiline-comment (start end) + "Parse a multiline comment as KDoc comment. + +The comment is assumed to be from START to END." + (save-excursion + (goto-char start) + (when (looking-at (rx "/*")) + (forward-char) + (skip-chars-forward "*")) + (setq start (point))) + (save-excursion + (goto-char end) + (when (and (eq (char-before) ?/) + (eq (char-before (1- (point))) ?*)) + (setq end (- end 2)))) + (let ((document (kotlin-mode--comment-node + :type 'document + :start start + :content-offset 0))) + (kotlin-mode--comment-node-add-child + document + (kotlin-mode--comment-node + :type 'multiline-comment + :start start + :content-offset (save-excursion + (goto-char start) + (skip-chars-forward "\s\t") + (forward-char 2) + (1+ (current-column))))) + (kotlin-mode--parse-kdoc-comment start end document))) + +(defun kotlin-mode--parse-kdoc-in-multiline-string (start end) + "Parse a multiline string as KDoc comment. + +The string is assumed to be from START to END." + (save-excursion + (goto-char start) + (skip-chars-forward "\"") + (setq start (point))) + (save-excursion + (goto-char end) + (skip-chars-backward "\"") + (setq end (point))) + (kotlin-mode--parse-kdoc-comment start + end + (kotlin-mode--comment-node + :type 'document + :start start + :content-offset 0))) + + +(defun kotlin-mode--parse-kdoc-comment (start end &optional document) + "Parse a KDoc comment. + +The comment is assumed to be from START to END. + +Results are appended to DOCUMENT as children. + +If DOCUMENT is ommitted, it is created." + (unless document + (setq document (kotlin-mode--comment-node + :type 'document + :start start + :content-offset 0))) + (save-excursion + (save-restriction + (narrow-to-region start end) + (goto-char start) + (while (not (eobp)) + (kotlin-mode--comment-parser-add-line document) + (forward-line) + (setq kotlin-mode--comment-parser-current-column 0)) + (kotlin-mode--comment-node-close-descendants document (bolp)) + (kotlin-mode--comment-node-finalize document (bolp)) + document))) + +(provide 'kotlin-mode-kdoc-comment-parser) + +;;; kotlin-mode-kdoc-comment-parser.el ends here diff --git a/kotlin-mode-lexer.el b/kotlin-mode-lexer.el index cf1b56d..2e8e93e 100644 --- a/kotlin-mode-lexer.el +++ b/kotlin-mode-lexer.el @@ -354,6 +354,15 @@ It have the type and the start position.") "Return the start position of the CHUNK." (and chunk (oref chunk start))) +(defun kotlin-mode--chunk-end (chunk) + "Return the end position of the CHUNK." + (save-excursion + (goto-char (oref chunk start)) + (if (kotlin-mode--chunk-comment-p chunk) + (forward-comment 1) + (kotlin-mode--forward-token)) + (point))) + (defun kotlin-mode--chunk-comment-p (chunk) "Return non-nil if the CHUNK is a comment." (and chunk @@ -455,6 +464,17 @@ If PARSER-STATE is given, it is used instead of (syntax-ppss)." (t nil)))) +(defun kotlin-mode--chunk-after-spaces () + "Return the chunk at the point or after spaces. + +If there is no chunk at the point nor after spaces, return nil." + (or (kotlin-mode--chunk-after) + (save-excursion + (skip-chars-forward " ") + (when (memq (char-after) '(?\" ?/)) + (forward-char) + (kotlin-mode--chunk-after))))) + ;; Syntax table (defvar kotlin-mode-syntax-table @@ -2584,6 +2604,17 @@ Newlines inside comments are ignored." (forward-comment (- (point))) (point))))) +(defun kotlin-mode--same-line-p (point1 point2) + "Return non-nil if POINT1 and POINT2 is on the same line. + +Return nil otherwise." + (= (save-excursion + (goto-char point1) + (line-beginning-position)) + (save-excursion + (goto-char point2) + (line-beginning-position)))) + (provide 'kotlin-mode-lexer) ;;; kotlin-mode-lexer.el ends here diff --git a/kotlin-mode.el b/kotlin-mode.el index f9ae1fb..9e09410 100644 --- a/kotlin-mode.el +++ b/kotlin-mode.el @@ -35,6 +35,7 @@ (require 'kotlin-mode-lexer) (require 'kotlin-mode-indent) +(require 'kotlin-mode-fill) (defgroup kotlin nil "A Kotlin major mode." @@ -358,18 +359,17 @@ and return non-nil. Return nil otherwise." ;; Multi-line comment (seq "/" (one-or-more "*")) ;; Middle of multi-line-comment - (seq (one-or-more "*") " ")) + (seq (one-or-more "*") (zero-or-one " "))) (zero-or-more (syntax whitespace))))) - (setq-local adaptive-fill-regexp - (rx (seq (zero-or-more (syntax whitespace)) - (or - ;; Single-line comment - (seq "/" (one-or-more "/")) - ;; Middle of multi-line-comment - (seq (one-or-more "*") " ")) - (zero-or-more (syntax whitespace))))) - (setq-local fill-indent-according-to-mode t) + (setq-local adaptive-fill-first-line-regexp ".*") + (setq-local adaptive-fill-function #'kotlin-mode--adaptive-fill) + (setq-local fill-indent-according-to-mode nil) (setq-local comment-multi-line t) + (setq-local fill-paragraph-function #'kotlin-mode--fill-paragraph) + (setq-local fill-forward-paragraph-function + #'kotlin-mode--fill-forward-paragraph) + (setq-local normal-auto-fill-function #'kotlin-mode--do-auto-fill) + (kotlin-mode--install-fill-region-as-paragraph-advice) (setq-local indent-line-function 'kotlin-mode--indent-line) diff --git a/scripts/run_linter.sh b/scripts/run_linter.sh index 61e55b2..dbf710b 100755 --- a/scripts/run_linter.sh +++ b/scripts/run_linter.sh @@ -14,6 +14,6 @@ rm -f *-autoloads.el || exit 1 -l elisp-lint.el \ --eval '(setq elisp-lint--debug t)' \ -f elisp-lint-files-batch \ - *.el || exit 1 + *.el "$@" || exit 1 rm -f *.elc test/*.elc || exit 1 rm -f *-autoloads.el || exit 1 diff --git a/scripts/run_linter_in_docker.sh b/scripts/run_linter_in_docker.sh index 44ebf6b..e6791ae 100755 --- a/scripts/run_linter_in_docker.sh +++ b/scripts/run_linter_in_docker.sh @@ -5,6 +5,7 @@ # Cask does't support Emacs 24. for version in 28 27 26 25 # 24 do + ARGS=$( [ "$version" -lt 26 ] && echo "--no-checkdoc" ) docker \ run \ --rm \ @@ -13,8 +14,8 @@ do --workdir="/src" \ --env=ELDEV_DIR=/src/.eldev \ --env=HOME=/tmp \ - silex/emacs:${version} \ - bash -c "/src/scripts/run_linter.sh" \ + silex/emacs:${version}-ci \ + bash -c "/src/scripts/run_linter.sh $ARGS" \ || exit 1 done diff --git a/scripts/run_test_in_docker.sh b/scripts/run_test_in_docker.sh index 2e3dc69..00cd5ac 100755 --- a/scripts/run_test_in_docker.sh +++ b/scripts/run_test_in_docker.sh @@ -15,7 +15,7 @@ do --workdir="/src" \ --env=ELDEV_DIR=/src/.eldev \ --env=HOME=/tmp \ - silex/emacs:${version} \ + silex/emacs:${version}-ci \ bash -c "/src/scripts/run_test.sh" \ || exit 1 done diff --git a/test/commonmark_cases.json b/test/commonmark_cases.json new file mode 100644 index 0000000..a557d68 --- /dev/null +++ b/test/commonmark_cases.json @@ -0,0 +1,2620 @@ +{ + "copyright": [ + "Derived from spec.txt of commonmark.js.\n", + "https://github.com/commonmark/commonmark.js\n", + "\n", + "---\n", + "title: CommonMark Spec\n", + "author: John MacFarlane\n", + "version: '0.30'\n", + "date: '2021-06-19'\n", + "license: '[CC-BY-SA 4.0](http://creativecommons.org/licenses/by-sa/4.0/)'\n", + "..." + ], + "cases": [ + { + "source": "\tfoo\tbaz\t\tbim\n", + "expected": "document 1-1\n code_block 1-1" + }, + { + "source": " \tfoo\tbaz\t\tbim\n", + "expected": "document 1-1\n code_block 1-1" + }, + { + "source": " a\ta\n ὐ\ta\n", + "expected": "document 1-2\n code_block 1-2" + }, + { + "source": " - foo\n\n\tbar\n", + "expected": "document 1-3\n list 1-3\n item 1-3\n paragraph 1-1\n paragraph 3-3" + }, + { + "source": "- foo\n\n\t\tbar\n", + "expected": "document 1-3\n list 1-3\n item 1-3\n paragraph 1-1\n code_block 3-3" + }, + { + "source": ">\t\tfoo\n", + "expected": "document 1-1\n block_quote 1-1\n code_block 1-1" + }, + { + "source": "-\t\tfoo\n", + "expected": "document 1-1\n list 1-1\n item 1-1\n code_block 1-1" + }, + { + "source": " foo\n\tbar\n", + "expected": "document 1-2\n code_block 1-2" + }, + { + "source": " - foo\n - bar\n\t - baz\n", + "expected": "document 1-3\n list 1-3\n item 1-3\n paragraph 1-1\n list 2-3\n item 2-3\n paragraph 2-2\n list 3-3\n item 3-3\n paragraph 3-3" + }, + { + "source": "#\tFoo\n", + "expected": "document 1-1\n heading 1-1" + }, + { + "source": "*\t*\t*\t\n", + "expected": "document 1-1\n thematic_break 1-1" + }, + { + "source": "\\!\\\"\\#\\$\\%\\&\\'\\(\\)\\*\\+\\,\\-\\.\\/\\:\\;\\<\\=\\>\\?\\@\\[\\\\\\]\\^\\_\\`\\{\\|\\}\\~\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "\\\t\\A\\a\\ \\3\\φ\\«\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "\\*not emphasized*\n\\
not a tag\n\\[not a link](/foo)\n\\`not code`\n1\\. not a list\n\\* not a list\n\\# not a heading\n\\[foo]: /url \"not a reference\"\n\\ö not a character entity\n", + "expected": "document 1-9\n paragraph 1-9" + }, + { + "source": "\\\\*emphasis*\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "foo\\\nbar\n", + "expected": "document 1-2\n paragraph 1-2" + }, + { + "source": "`` \\[\\` ``\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": " \\[\\]\n", + "expected": "document 1-1\n code_block 1-1" + }, + { + "source": "~~~\n\\[\\]\n~~~\n", + "expected": "document 1-3\n code_block 1-3" + }, + { + "source": "\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "\n", + "expected": "document 1-1\n html_block 1-1" + }, + { + "source": "[foo](/bar\\* \"ti\\*tle\")\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "[foo]\n\n[foo]: /bar\\* \"ti\\*tle\"\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "``` foo\\+bar\nfoo\n```\n", + "expected": "document 1-3\n code_block 1-3" + }, + { + "source": "  & © Æ Ď\n¾ ℋ ⅆ\n∲ ≧̸\n", + "expected": "document 1-3\n paragraph 1-3" + }, + { + "source": "# Ӓ Ϡ �\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "" ആ ಫ\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "  &x; &#; &#x;\n�\n&#abcdef0;\n&ThisIsNotDefined; &hi?;\n", + "expected": "document 1-4\n paragraph 1-4" + }, + { + "source": "©\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "&MadeUpEntity;\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "\n", + "expected": "document 1-1\n html_block 1-1" + }, + { + "source": "[foo](/föö \"föö\")\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "[foo]\n\n[foo]: /föö \"föö\"\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "``` föö\nfoo\n```\n", + "expected": "document 1-3\n code_block 1-3" + }, + { + "source": "`föö`\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": " föfö\n", + "expected": "document 1-1\n code_block 1-1" + }, + { + "source": "*foo*\n*foo*\n", + "expected": "document 1-2\n paragraph 1-2" + }, + { + "source": "* foo\n\n* foo\n", + "expected": "document 1-3\n paragraph 1-1\n list 3-3\n item 3-3\n paragraph 3-3" + }, + { + "source": "foo bar\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": " foo\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "[a](url "tit")\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "- `one\n- two`\n", + "expected": "document 1-2\n list 1-2\n item 1-1\n paragraph 1-1\n item 2-2\n paragraph 2-2" + }, + { + "source": "***\n---\n___\n", + "expected": "document 1-3\n thematic_break 1-1\n thematic_break 2-2\n thematic_break 3-3" + }, + { + "source": "+++\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "===\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "--\n**\n__\n", + "expected": "document 1-3\n paragraph 1-3" + }, + { + "source": " ***\n ***\n ***\n", + "expected": "document 1-3\n thematic_break 1-1\n thematic_break 2-2\n thematic_break 3-3" + }, + { + "source": " ***\n", + "expected": "document 1-1\n code_block 1-1" + }, + { + "source": "Foo\n ***\n", + "expected": "document 1-2\n paragraph 1-2" + }, + { + "source": "_____________________________________\n", + "expected": "document 1-1\n thematic_break 1-1" + }, + { + "source": " - - -\n", + "expected": "document 1-1\n thematic_break 1-1" + }, + { + "source": " ** * ** * ** * **\n", + "expected": "document 1-1\n thematic_break 1-1" + }, + { + "source": "- - - -\n", + "expected": "document 1-1\n thematic_break 1-1" + }, + { + "source": "- - - - \n", + "expected": "document 1-1\n thematic_break 1-1" + }, + { + "source": "_ _ _ _ a\n\na------\n\n---a---\n", + "expected": "document 1-5\n paragraph 1-1\n paragraph 3-3\n paragraph 5-5" + }, + { + "source": " *-*\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "- foo\n***\n- bar\n", + "expected": "document 1-3\n list 1-1\n item 1-1\n paragraph 1-1\n thematic_break 2-2\n list 3-3\n item 3-3\n paragraph 3-3" + }, + { + "source": "Foo\n***\nbar\n", + "expected": "document 1-3\n paragraph 1-1\n thematic_break 2-2\n paragraph 3-3" + }, + { + "source": "Foo\n---\nbar\n", + "expected": "document 1-3\n heading 1-2\n paragraph 3-3" + }, + { + "source": "* Foo\n* * *\n* Bar\n", + "expected": "document 1-3\n list 1-1\n item 1-1\n paragraph 1-1\n thematic_break 2-2\n list 3-3\n item 3-3\n paragraph 3-3" + }, + { + "source": "- Foo\n- * * *\n", + "expected": "document 1-2\n list 1-2\n item 1-1\n paragraph 1-1\n item 2-2\n thematic_break 2-2" + }, + { + "source": "# foo\n## foo\n### foo\n#### foo\n##### foo\n###### foo\n", + "expected": "document 1-6\n heading 1-1\n heading 2-2\n heading 3-3\n heading 4-4\n heading 5-5\n heading 6-6" + }, + { + "source": "####### foo\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "#5 bolt\n\n#hashtag\n", + "expected": "document 1-3\n paragraph 1-1\n paragraph 3-3" + }, + { + "source": "\\## foo\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "# foo *bar* \\*baz\\*\n", + "expected": "document 1-1\n heading 1-1" + }, + { + "source": "# foo \n", + "expected": "document 1-1\n heading 1-1" + }, + { + "source": " ### foo\n ## foo\n # foo\n", + "expected": "document 1-3\n heading 1-1\n heading 2-2\n heading 3-3" + }, + { + "source": " # foo\n", + "expected": "document 1-1\n code_block 1-1" + }, + { + "source": "foo\n # bar\n", + "expected": "document 1-2\n paragraph 1-2" + }, + { + "source": "## foo ##\n ### bar ###\n", + "expected": "document 1-2\n heading 1-1\n heading 2-2" + }, + { + "source": "# foo ##################################\n##### foo ##\n", + "expected": "document 1-2\n heading 1-1\n heading 2-2" + }, + { + "source": "### foo ### \n", + "expected": "document 1-1\n heading 1-1" + }, + { + "source": "### foo ### b\n", + "expected": "document 1-1\n heading 1-1" + }, + { + "source": "# foo#\n", + "expected": "document 1-1\n heading 1-1" + }, + { + "source": "### foo \\###\n## foo #\\##\n# foo \\#\n", + "expected": "document 1-3\n heading 1-1\n heading 2-2\n heading 3-3" + }, + { + "source": "****\n## foo\n****\n", + "expected": "document 1-3\n thematic_break 1-1\n heading 2-2\n thematic_break 3-3" + }, + { + "source": "Foo bar\n# baz\nBar foo\n", + "expected": "document 1-3\n paragraph 1-1\n heading 2-2\n paragraph 3-3" + }, + { + "source": "## \n#\n### ###\n", + "expected": "document 1-3\n heading 1-1\n heading 2-2\n heading 3-3" + }, + { + "source": "Foo *bar*\n=========\n\nFoo *bar*\n---------\n", + "expected": "document 1-5\n heading 1-2\n heading 4-5" + }, + { + "source": "Foo *bar\nbaz*\n====\n", + "expected": "document 1-3\n heading 1-3" + }, + { + "source": " Foo *bar\nbaz*\t\n====\n", + "expected": "document 1-3\n heading 1-3" + }, + { + "source": "Foo\n-------------------------\n\nFoo\n=\n", + "expected": "document 1-5\n heading 1-2\n heading 4-5" + }, + { + "source": " Foo\n---\n\n Foo\n-----\n\n Foo\n ===\n", + "expected": "document 1-8\n heading 1-2\n heading 4-5\n heading 7-8" + }, + { + "source": " Foo\n ---\n\n Foo\n---\n", + "expected": "document 1-5\n code_block 1-4\n thematic_break 5-5" + }, + { + "source": "Foo\n ---- \n", + "expected": "document 1-2\n heading 1-2" + }, + { + "source": "Foo\n ---\n", + "expected": "document 1-2\n paragraph 1-2" + }, + { + "source": "Foo\n= =\n\nFoo\n--- -\n", + "expected": "document 1-5\n paragraph 1-2\n paragraph 4-4\n thematic_break 5-5" + }, + { + "source": "Foo \n-----\n", + "expected": "document 1-2\n heading 1-2" + }, + { + "source": "Foo\\\n----\n", + "expected": "document 1-2\n heading 1-2" + }, + { + "source": "`Foo\n----\n`\n\n\n", + "expected": "document 1-7\n heading 1-2\n paragraph 3-3\n heading 5-6\n paragraph 7-7" + }, + { + "source": "> Foo\n---\n", + "expected": "document 1-2\n block_quote 1-1\n paragraph 1-1\n thematic_break 2-2" + }, + { + "source": "> foo\nbar\n===\n", + "expected": "document 1-3\n block_quote 1-3\n paragraph 1-3" + }, + { + "source": "- Foo\n---\n", + "expected": "document 1-2\n list 1-1\n item 1-1\n paragraph 1-1\n thematic_break 2-2" + }, + { + "source": "Foo\nBar\n---\n", + "expected": "document 1-3\n heading 1-3" + }, + { + "source": "---\nFoo\n---\nBar\n---\nBaz\n", + "expected": "document 1-6\n thematic_break 1-1\n heading 2-3\n heading 4-5\n paragraph 6-6" + }, + { + "source": "\n====\n", + "expected": "document 1-2\n paragraph 2-2" + }, + { + "source": "---\n---\n", + "expected": "document 1-2\n thematic_break 1-1\n thematic_break 2-2" + }, + { + "source": "- foo\n-----\n", + "expected": "document 1-2\n list 1-1\n item 1-1\n paragraph 1-1\n thematic_break 2-2" + }, + { + "source": " foo\n---\n", + "expected": "document 1-2\n code_block 1-1\n thematic_break 2-2" + }, + { + "source": "> foo\n-----\n", + "expected": "document 1-2\n block_quote 1-1\n paragraph 1-1\n thematic_break 2-2" + }, + { + "source": "\\> foo\n------\n", + "expected": "document 1-2\n heading 1-2" + }, + { + "source": "Foo\n\nbar\n---\nbaz\n", + "expected": "document 1-5\n paragraph 1-1\n heading 3-4\n paragraph 5-5" + }, + { + "source": "Foo\nbar\n\n---\n\nbaz\n", + "expected": "document 1-6\n paragraph 1-2\n thematic_break 4-4\n paragraph 6-6" + }, + { + "source": "Foo\nbar\n* * *\nbaz\n", + "expected": "document 1-4\n paragraph 1-2\n thematic_break 3-3\n paragraph 4-4" + }, + { + "source": "Foo\nbar\n\\---\nbaz\n", + "expected": "document 1-4\n paragraph 1-4" + }, + { + "source": " a simple\n indented code block\n", + "expected": "document 1-2\n code_block 1-2" + }, + { + "source": " - foo\n\n bar\n", + "expected": "document 1-3\n list 1-3\n item 1-3\n paragraph 1-1\n paragraph 3-3" + }, + { + "source": "1. foo\n\n - bar\n", + "expected": "document 1-3\n list 1-3\n item 1-3\n paragraph 1-1\n list 3-3\n item 3-3\n paragraph 3-3" + }, + { + "source": " \n *hi*\n\n - one\n", + "expected": "document 1-4\n code_block 1-4" + }, + { + "source": " chunk1\n\n chunk2\n \n \n \n chunk3\n", + "expected": "document 1-7\n code_block 1-7" + }, + { + "source": " chunk1\n \n chunk2\n", + "expected": "document 1-3\n code_block 1-3" + }, + { + "source": "Foo\n bar\n\n", + "expected": "document 1-3\n paragraph 1-2" + }, + { + "source": " foo\nbar\n", + "expected": "document 1-2\n code_block 1-1\n paragraph 2-2" + }, + { + "source": "# Heading\n foo\nHeading\n------\n foo\n----\n", + "expected": "document 1-6\n heading 1-1\n code_block 2-2\n heading 3-4\n code_block 5-5\n thematic_break 6-6" + }, + { + "source": " foo\n bar\n", + "expected": "document 1-2\n code_block 1-2" + }, + { + "source": "\n \n foo\n \n\n", + "expected": "document 1-5\n code_block 3-3" + }, + { + "source": " foo \n", + "expected": "document 1-1\n code_block 1-1" + }, + { + "source": "```\n<\n >\n```\n", + "expected": "document 1-4\n code_block 1-4" + }, + { + "source": "~~~\n<\n >\n~~~\n", + "expected": "document 1-4\n code_block 1-4" + }, + { + "source": "``\nfoo\n``\n", + "expected": "document 1-3\n paragraph 1-3" + }, + { + "source": "```\naaa\n~~~\n```\n", + "expected": "document 1-4\n code_block 1-4" + }, + { + "source": "~~~\naaa\n```\n~~~\n", + "expected": "document 1-4\n code_block 1-4" + }, + { + "source": "````\naaa\n```\n``````\n", + "expected": "document 1-4\n code_block 1-4" + }, + { + "source": "~~~~\naaa\n~~~\n~~~~\n", + "expected": "document 1-4\n code_block 1-4" + }, + { + "source": "```\n", + "expected": "document 1-1\n code_block 1-1" + }, + { + "source": "`````\n\n```\naaa\n", + "expected": "document 1-4\n code_block 1-4" + }, + { + "source": "> ```\n> aaa\n\nbbb\n", + "expected": "document 1-4\n block_quote 1-2\n code_block 1-2\n paragraph 4-4" + }, + { + "source": "```\n\n \n```\n", + "expected": "document 1-4\n code_block 1-4" + }, + { + "source": "```\n```\n", + "expected": "document 1-2\n code_block 1-2" + }, + { + "source": " ```\n aaa\naaa\n```\n", + "expected": "document 1-4\n code_block 1-4" + }, + { + "source": " ```\naaa\n aaa\naaa\n ```\n", + "expected": "document 1-5\n code_block 1-5" + }, + { + "source": " ```\n aaa\n aaa\n aaa\n ```\n", + "expected": "document 1-5\n code_block 1-5" + }, + { + "source": " ```\n aaa\n ```\n", + "expected": "document 1-3\n code_block 1-3" + }, + { + "source": "```\naaa\n ```\n", + "expected": "document 1-3\n code_block 1-3" + }, + { + "source": " ```\naaa\n ```\n", + "expected": "document 1-3\n code_block 1-3" + }, + { + "source": "```\naaa\n ```\n", + "expected": "document 1-3\n code_block 1-3" + }, + { + "source": "``` ```\naaa\n", + "expected": "document 1-2\n paragraph 1-2" + }, + { + "source": "~~~~~~\naaa\n~~~ ~~\n", + "expected": "document 1-3\n code_block 1-3" + }, + { + "source": "foo\n```\nbar\n```\nbaz\n", + "expected": "document 1-5\n paragraph 1-1\n code_block 2-4\n paragraph 5-5" + }, + { + "source": "foo\n---\n~~~\nbar\n~~~\n# baz\n", + "expected": "document 1-6\n heading 1-2\n code_block 3-5\n heading 6-6" + }, + { + "source": "```ruby\ndef foo(x)\n return 3\nend\n```\n", + "expected": "document 1-5\n code_block 1-5" + }, + { + "source": "~~~~ ruby startline=3 $%@#$\ndef foo(x)\n return 3\nend\n~~~~~~~\n", + "expected": "document 1-5\n code_block 1-5" + }, + { + "source": "````;\n````\n", + "expected": "document 1-2\n code_block 1-2" + }, + { + "source": "``` aa ```\nfoo\n", + "expected": "document 1-2\n paragraph 1-2" + }, + { + "source": "~~~ aa ``` ~~~\nfoo\n~~~\n", + "expected": "document 1-3\n code_block 1-3" + }, + { + "source": "```\n``` aaa\n```\n", + "expected": "document 1-3\n code_block 1-3" + }, + { + "source": "
\n
\n**Hello**,\n\n_world_.\n
\n
\n", + "expected": "document 1-7\n html_block 1-3\n paragraph 5-6\n html_block 7-7" + }, + { + "source": "\n \n \n \n
\n hi\n
\n\nokay.\n", + "expected": "document 1-9\n html_block 1-7\n paragraph 9-9" + }, + { + "source": "
\n *hello*\n \n", + "expected": "document 1-3\n html_block 1-3" + }, + { + "source": "
\n*foo*\n", + "expected": "document 1-2\n html_block 1-2" + }, + { + "source": "
\n\n*Markdown*\n\n
\n", + "expected": "document 1-5\n html_block 1-1\n paragraph 3-3\n html_block 5-5" + }, + { + "source": "
\n
\n", + "expected": "document 1-3\n html_block 1-3" + }, + { + "source": "
\n
\n", + "expected": "document 1-3\n html_block 1-3" + }, + { + "source": "
\n*foo*\n\n*bar*\n", + "expected": "document 1-4\n html_block 1-2\n paragraph 4-4" + }, + { + "source": "\n", + "expected": "document 1-1\n html_block 1-1" + }, + { + "source": "
\nfoo\n
\n", + "expected": "document 1-3\n html_block 1-3" + }, + { + "source": "
\n``` c\nint x = 33;\n```\n", + "expected": "document 1-4\n html_block 1-4" + }, + { + "source": "\n*bar*\n\n", + "expected": "document 1-3\n html_block 1-3" + }, + { + "source": "\n*bar*\n\n", + "expected": "document 1-3\n html_block 1-3" + }, + { + "source": "\n*bar*\n\n", + "expected": "document 1-3\n html_block 1-3" + }, + { + "source": "\n*bar*\n", + "expected": "document 1-2\n html_block 1-2" + }, + { + "source": "\n*foo*\n\n", + "expected": "document 1-3\n html_block 1-3" + }, + { + "source": "\n\n*foo*\n\n\n", + "expected": "document 1-5\n html_block 1-1\n paragraph 3-3\n html_block 5-5" + }, + { + "source": "*foo*\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "
\nimport Text.HTML.TagSoup\n\nmain :: IO ()\nmain = print $ parseTags tags\n
\nokay\n", + "expected": "document 1-7\n html_block 1-6\n paragraph 7-7" + }, + { + "source": "\nokay\n", + "expected": "document 1-6\n html_block 1-5\n paragraph 6-6" + }, + { + "source": "\n", + "expected": "document 1-7\n html_block 1-7" + }, + { + "source": "\nh1 {color:red;}\n\np {color:blue;}\n\nokay\n", + "expected": "document 1-7\n html_block 1-6\n paragraph 7-7" + }, + { + "source": "\n\nfoo\n", + "expected": "document 1-4\n html_block 1-4" + }, + { + "source": ">
\n> foo\n\nbar\n", + "expected": "document 1-4\n block_quote 1-2\n html_block 1-2\n paragraph 4-4" + }, + { + "source": "-
\n- foo\n", + "expected": "document 1-2\n list 1-2\n item 1-1\n html_block 1-1\n item 2-2\n paragraph 2-2" + }, + { + "source": "\n*foo*\n", + "expected": "document 1-2\n html_block 1-1\n paragraph 2-2" + }, + { + "source": "*bar*\n*baz*\n", + "expected": "document 1-2\n html_block 1-1\n paragraph 2-2" + }, + { + "source": "1. *bar*\n", + "expected": "document 1-3\n html_block 1-3" + }, + { + "source": "\nokay\n", + "expected": "document 1-5\n html_block 1-4\n paragraph 5-5" + }, + { + "source": "';\n\n?>\nokay\n", + "expected": "document 1-6\n html_block 1-5\n paragraph 6-6" + }, + { + "source": "\n", + "expected": "document 1-1\n html_block 1-1" + }, + { + "source": "\nokay\n", + "expected": "document 1-13\n html_block 1-12\n paragraph 13-13" + }, + { + "source": " \n\n \n", + "expected": "document 1-3\n html_block 1-1\n code_block 3-3" + }, + { + "source": "
\n\n
\n", + "expected": "document 1-3\n html_block 1-1\n code_block 3-3" + }, + { + "source": "Foo\n
\nbar\n
\n", + "expected": "document 1-4\n paragraph 1-1\n html_block 2-4" + }, + { + "source": "
\nbar\n
\n*foo*\n", + "expected": "document 1-4\n html_block 1-4" + }, + { + "source": "Foo\n\nbaz\n", + "expected": "document 1-3\n paragraph 1-3" + }, + { + "source": "
\n\n*Emphasized* text.\n\n
\n", + "expected": "document 1-5\n html_block 1-1\n paragraph 3-3\n html_block 5-5" + }, + { + "source": "
\n*Emphasized* text.\n
\n", + "expected": "document 1-3\n html_block 1-3" + }, + { + "source": "\n\n\n\n\n\n\n\n
\nHi\n
\n", + "expected": "document 1-11\n html_block 1-1\n html_block 3-3\n html_block 5-7\n html_block 9-9\n html_block 11-11" + }, + { + "source": "\n\n \n\n \n\n \n\n
\n Hi\n
\n", + "expected": "document 1-11\n html_block 1-1\n html_block 3-3\n code_block 5-7\n html_block 9-9\n html_block 11-11" + }, + { + "source": "[foo]: /url \"title\"\n\n[foo]\n", + "expected": "document 1-3\n paragraph 3-3" + }, + { + "source": " [foo]: \n /url \n 'the title' \n\n[foo]\n", + "expected": "document 1-5\n paragraph 5-5" + }, + { + "source": "[Foo*bar\\]]:my_(url) 'title (with parens)'\n\n[Foo*bar\\]]\n", + "expected": "document 1-3\n paragraph 3-3" + }, + { + "source": "[Foo bar]:\n\n'title'\n\n[Foo bar]\n", + "expected": "document 1-5\n paragraph 5-5" + }, + { + "source": "[foo]: /url '\ntitle\nline1\nline2\n'\n\n[foo]\n", + "expected": "document 1-7\n paragraph 7-7" + }, + { + "source": "[foo]: /url 'title\n\nwith blank line'\n\n[foo]\n", + "expected": "document 1-5\n paragraph 1-1\n paragraph 3-3\n paragraph 5-5" + }, + { + "source": "[foo]:\n/url\n\n[foo]\n", + "expected": "document 1-4\n paragraph 4-4" + }, + { + "source": "[foo]:\n\n[foo]\n", + "expected": "document 1-3\n paragraph 1-1\n paragraph 3-3" + }, + { + "source": "[foo]: <>\n\n[foo]\n", + "expected": "document 1-3\n paragraph 3-3" + }, + { + "source": "[foo]: (baz)\n\n[foo]\n", + "expected": "document 1-3\n paragraph 1-1\n paragraph 3-3" + }, + { + "source": "[foo]: /url\\bar\\*baz \"foo\\\"bar\\baz\"\n\n[foo]\n", + "expected": "document 1-3\n paragraph 3-3" + }, + { + "source": "[foo]\n\n[foo]: url\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "[foo]\n\n[foo]: first\n[foo]: second\n", + "expected": "document 1-4\n paragraph 1-1" + }, + { + "source": "[FOO]: /url\n\n[Foo]\n", + "expected": "document 1-3\n paragraph 3-3" + }, + { + "source": "[ΑΓΩ]: /φου\n\n[αγω]\n", + "expected": "document 1-3\n paragraph 3-3" + }, + { + "source": "[foo]: /url\n", + "expected": "document 1-1" + }, + { + "source": "[\nfoo\n]: /url\nbar\n", + "expected": "document 1-4\n paragraph 4-4" + }, + { + "source": "[foo]: /url \"title\" ok\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "[foo]: /url\n\"title\" ok\n", + "expected": "document 1-2\n paragraph 2-2" + }, + { + "source": " [foo]: /url \"title\"\n\n[foo]\n", + "expected": "document 1-3\n code_block 1-1\n paragraph 3-3" + }, + { + "source": "```\n[foo]: /url\n```\n\n[foo]\n", + "expected": "document 1-5\n code_block 1-3\n paragraph 5-5" + }, + { + "source": "Foo\n[bar]: /baz\n\n[bar]\n", + "expected": "document 1-4\n paragraph 1-2\n paragraph 4-4" + }, + { + "source": "# [Foo]\n[foo]: /url\n> bar\n", + "expected": "document 1-3\n heading 1-1\n block_quote 3-3\n paragraph 3-3" + }, + { + "source": "[foo]: /url\nbar\n===\n[foo]\n", + "expected": "document 1-4\n heading 1-3\n paragraph 4-4" + }, + { + "source": "[foo]: /url\n===\n[foo]\n", + "expected": "document 1-3\n paragraph 1-3" + }, + { + "source": "[foo]: /foo-url \"foo\"\n[bar]: /bar-url\n \"bar\"\n[baz]: /baz-url\n\n[foo],\n[bar],\n[baz]\n", + "expected": "document 1-8\n paragraph 6-8" + }, + { + "source": "[foo]\n\n> [foo]: /url\n", + "expected": "document 1-3\n paragraph 1-1\n block_quote 3-3" + }, + { + "source": "aaa\n\nbbb\n", + "expected": "document 1-3\n paragraph 1-1\n paragraph 3-3" + }, + { + "source": "aaa\nbbb\n\nccc\nddd\n", + "expected": "document 1-5\n paragraph 1-2\n paragraph 4-5" + }, + { + "source": "aaa\n\n\nbbb\n", + "expected": "document 1-4\n paragraph 1-1\n paragraph 4-4" + }, + { + "source": " aaa\n bbb\n", + "expected": "document 1-2\n paragraph 1-2" + }, + { + "source": "aaa\n bbb\n ccc\n", + "expected": "document 1-3\n paragraph 1-3" + }, + { + "source": " aaa\nbbb\n", + "expected": "document 1-2\n paragraph 1-2" + }, + { + "source": " aaa\nbbb\n", + "expected": "document 1-2\n code_block 1-1\n paragraph 2-2" + }, + { + "source": "aaa \nbbb \n", + "expected": "document 1-2\n paragraph 1-2" + }, + { + "source": " \n\naaa\n \n\n# aaa\n\n \n", + "expected": "document 1-8\n paragraph 3-3\n heading 6-6" + }, + { + "source": "> # Foo\n> bar\n> baz\n", + "expected": "document 1-3\n block_quote 1-3\n heading 1-1\n paragraph 2-3" + }, + { + "source": "># Foo\n>bar\n> baz\n", + "expected": "document 1-3\n block_quote 1-3\n heading 1-1\n paragraph 2-3" + }, + { + "source": " > # Foo\n > bar\n > baz\n", + "expected": "document 1-3\n block_quote 1-3\n heading 1-1\n paragraph 2-3" + }, + { + "source": " > # Foo\n > bar\n > baz\n", + "expected": "document 1-3\n code_block 1-3" + }, + { + "source": "> # Foo\n> bar\nbaz\n", + "expected": "document 1-3\n block_quote 1-3\n heading 1-1\n paragraph 2-3" + }, + { + "source": "> bar\nbaz\n> foo\n", + "expected": "document 1-3\n block_quote 1-3\n paragraph 1-3" + }, + { + "source": "> foo\n---\n", + "expected": "document 1-2\n block_quote 1-1\n paragraph 1-1\n thematic_break 2-2" + }, + { + "source": "> - foo\n- bar\n", + "expected": "document 1-2\n block_quote 1-1\n list 1-1\n item 1-1\n paragraph 1-1\n list 2-2\n item 2-2\n paragraph 2-2" + }, + { + "source": "> foo\n bar\n", + "expected": "document 1-2\n block_quote 1-1\n code_block 1-1\n code_block 2-2" + }, + { + "source": "> ```\nfoo\n```\n", + "expected": "document 1-3\n block_quote 1-1\n code_block 1-1\n paragraph 2-2\n code_block 3-3" + }, + { + "source": "> foo\n - bar\n", + "expected": "document 1-2\n block_quote 1-2\n paragraph 1-2" + }, + { + "source": ">\n", + "expected": "document 1-1\n block_quote 1-1" + }, + { + "source": ">\n> \n> \n", + "expected": "document 1-3\n block_quote 1-3" + }, + { + "source": ">\n> foo\n> \n", + "expected": "document 1-3\n block_quote 1-3\n paragraph 2-2" + }, + { + "source": "> foo\n\n> bar\n", + "expected": "document 1-3\n block_quote 1-1\n paragraph 1-1\n block_quote 3-3\n paragraph 3-3" + }, + { + "source": "> foo\n> bar\n", + "expected": "document 1-2\n block_quote 1-2\n paragraph 1-2" + }, + { + "source": "> foo\n>\n> bar\n", + "expected": "document 1-3\n block_quote 1-3\n paragraph 1-1\n paragraph 3-3" + }, + { + "source": "foo\n> bar\n", + "expected": "document 1-2\n paragraph 1-1\n block_quote 2-2\n paragraph 2-2" + }, + { + "source": "> aaa\n***\n> bbb\n", + "expected": "document 1-3\n block_quote 1-1\n paragraph 1-1\n thematic_break 2-2\n block_quote 3-3\n paragraph 3-3" + }, + { + "source": "> bar\nbaz\n", + "expected": "document 1-2\n block_quote 1-2\n paragraph 1-2" + }, + { + "source": "> bar\n\nbaz\n", + "expected": "document 1-3\n block_quote 1-1\n paragraph 1-1\n paragraph 3-3" + }, + { + "source": "> bar\n>\nbaz\n", + "expected": "document 1-3\n block_quote 1-2\n paragraph 1-1\n paragraph 3-3" + }, + { + "source": "> > > foo\nbar\n", + "expected": "document 1-2\n block_quote 1-2\n block_quote 1-2\n block_quote 1-2\n paragraph 1-2" + }, + { + "source": ">>> foo\n> bar\n>>baz\n", + "expected": "document 1-3\n block_quote 1-3\n block_quote 1-3\n block_quote 1-3\n paragraph 1-3" + }, + { + "source": "> code\n\n> not code\n", + "expected": "document 1-3\n block_quote 1-1\n code_block 1-1\n block_quote 3-3\n paragraph 3-3" + }, + { + "source": "A paragraph\nwith two lines.\n\n indented code\n\n> A block quote.\n", + "expected": "document 1-6\n paragraph 1-2\n code_block 4-4\n block_quote 6-6\n paragraph 6-6" + }, + { + "source": "1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n", + "expected": "document 1-6\n list 1-6\n item 1-6\n paragraph 1-2\n code_block 4-4\n block_quote 6-6\n paragraph 6-6" + }, + { + "source": "- one\n\n two\n", + "expected": "document 1-3\n list 1-1\n item 1-1\n paragraph 1-1\n paragraph 3-3" + }, + { + "source": "- one\n\n two\n", + "expected": "document 1-3\n list 1-3\n item 1-3\n paragraph 1-1\n paragraph 3-3" + }, + { + "source": " - one\n\n two\n", + "expected": "document 1-3\n list 1-1\n item 1-1\n paragraph 1-1\n code_block 3-3" + }, + { + "source": " - one\n\n two\n", + "expected": "document 1-3\n list 1-3\n item 1-3\n paragraph 1-1\n paragraph 3-3" + }, + { + "source": " > > 1. one\n>>\n>> two\n", + "expected": "document 1-3\n block_quote 1-3\n block_quote 1-3\n list 1-3\n item 1-3\n paragraph 1-1\n paragraph 3-3" + }, + { + "source": ">>- one\n>>\n > > two\n", + "expected": "document 1-3\n block_quote 1-3\n block_quote 1-3\n list 1-1\n item 1-1\n paragraph 1-1\n paragraph 3-3" + }, + { + "source": "-one\n\n2.two\n", + "expected": "document 1-3\n paragraph 1-1\n paragraph 3-3" + }, + { + "source": "- foo\n\n\n bar\n", + "expected": "document 1-4\n list 1-4\n item 1-4\n paragraph 1-1\n paragraph 4-4" + }, + { + "source": "1. foo\n\n ```\n bar\n ```\n\n baz\n\n > bam\n", + "expected": "document 1-9\n list 1-9\n item 1-9\n paragraph 1-1\n code_block 3-5\n paragraph 7-7\n block_quote 9-9\n paragraph 9-9" + }, + { + "source": "- Foo\n\n bar\n\n\n baz\n", + "expected": "document 1-6\n list 1-6\n item 1-6\n paragraph 1-1\n code_block 3-6" + }, + { + "source": "123456789. ok\n", + "expected": "document 1-1\n list 1-1\n item 1-1\n paragraph 1-1" + }, + { + "source": "1234567890. not ok\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "0. ok\n", + "expected": "document 1-1\n list 1-1\n item 1-1\n paragraph 1-1" + }, + { + "source": "003. ok\n", + "expected": "document 1-1\n list 1-1\n item 1-1\n paragraph 1-1" + }, + { + "source": "-1. not ok\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "- foo\n\n bar\n", + "expected": "document 1-3\n list 1-3\n item 1-3\n paragraph 1-1\n code_block 3-3" + }, + { + "source": " 10. foo\n\n bar\n", + "expected": "document 1-3\n list 1-3\n item 1-3\n paragraph 1-1\n code_block 3-3" + }, + { + "source": " indented code\n\nparagraph\n\n more code\n", + "expected": "document 1-5\n code_block 1-1\n paragraph 3-3\n code_block 5-5" + }, + { + "source": "1. indented code\n\n paragraph\n\n more code\n", + "expected": "document 1-5\n list 1-5\n item 1-5\n code_block 1-1\n paragraph 3-3\n code_block 5-5" + }, + { + "source": "1. indented code\n\n paragraph\n\n more code\n", + "expected": "document 1-5\n list 1-5\n item 1-5\n code_block 1-1\n paragraph 3-3\n code_block 5-5" + }, + { + "source": " foo\n\nbar\n", + "expected": "document 1-3\n paragraph 1-1\n paragraph 3-3" + }, + { + "source": "- foo\n\n bar\n", + "expected": "document 1-3\n list 1-1\n item 1-1\n paragraph 1-1\n paragraph 3-3" + }, + { + "source": "- foo\n\n bar\n", + "expected": "document 1-3\n list 1-3\n item 1-3\n paragraph 1-1\n paragraph 3-3" + }, + { + "source": "-\n foo\n-\n ```\n bar\n ```\n-\n baz\n", + "expected": "document 1-8\n list 1-8\n item 1-2\n paragraph 2-2\n item 3-6\n code_block 4-6\n item 7-8\n code_block 8-8" + }, + { + "source": "- \n foo\n", + "expected": "document 1-2\n list 1-2\n item 1-2\n paragraph 2-2" + }, + { + "source": "-\n\n foo\n", + "expected": "document 1-3\n list 1-1\n item 1-1\n paragraph 3-3" + }, + { + "source": "- foo\n-\n- bar\n", + "expected": "document 1-3\n list 1-3\n item 1-1\n paragraph 1-1\n item 2-2\n item 3-3\n paragraph 3-3" + }, + { + "source": "- foo\n- \n- bar\n", + "expected": "document 1-3\n list 1-3\n item 1-1\n paragraph 1-1\n item 2-2\n item 3-3\n paragraph 3-3" + }, + { + "source": "1. foo\n2.\n3. bar\n", + "expected": "document 1-3\n list 1-3\n item 1-1\n paragraph 1-1\n item 2-2\n item 3-3\n paragraph 3-3" + }, + { + "source": "*\n", + "expected": "document 1-1\n list 1-1\n item 1-1" + }, + { + "source": "foo\n*\n\nfoo\n1.\n", + "expected": "document 1-5\n paragraph 1-2\n paragraph 4-5" + }, + { + "source": " 1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n", + "expected": "document 1-6\n list 1-6\n item 1-6\n paragraph 1-2\n code_block 4-4\n block_quote 6-6\n paragraph 6-6" + }, + { + "source": " 1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n", + "expected": "document 1-6\n list 1-6\n item 1-6\n paragraph 1-2\n code_block 4-4\n block_quote 6-6\n paragraph 6-6" + }, + { + "source": " 1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n", + "expected": "document 1-6\n list 1-6\n item 1-6\n paragraph 1-2\n code_block 4-4\n block_quote 6-6\n paragraph 6-6" + }, + { + "source": " 1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n", + "expected": "document 1-6\n code_block 1-6" + }, + { + "source": " 1. A paragraph\nwith two lines.\n\n indented code\n\n > A block quote.\n", + "expected": "document 1-6\n list 1-6\n item 1-6\n paragraph 1-2\n code_block 4-4\n block_quote 6-6\n paragraph 6-6" + }, + { + "source": " 1. A paragraph\n with two lines.\n", + "expected": "document 1-2\n list 1-2\n item 1-2\n paragraph 1-2" + }, + { + "source": "> 1. > Blockquote\ncontinued here.\n", + "expected": "document 1-2\n block_quote 1-2\n list 1-2\n item 1-2\n block_quote 1-2\n paragraph 1-2" + }, + { + "source": "> 1. > Blockquote\n> continued here.\n", + "expected": "document 1-2\n block_quote 1-2\n list 1-2\n item 1-2\n block_quote 1-2\n paragraph 1-2" + }, + { + "source": "- foo\n - bar\n - baz\n - boo\n", + "expected": "document 1-4\n list 1-4\n item 1-4\n paragraph 1-1\n list 2-4\n item 2-4\n paragraph 2-2\n list 3-4\n item 3-4\n paragraph 3-3\n list 4-4\n item 4-4\n paragraph 4-4" + }, + { + "source": "- foo\n - bar\n - baz\n - boo\n", + "expected": "document 1-4\n list 1-4\n item 1-1\n paragraph 1-1\n item 2-2\n paragraph 2-2\n item 3-3\n paragraph 3-3\n item 4-4\n paragraph 4-4" + }, + { + "source": "10) foo\n - bar\n", + "expected": "document 1-2\n list 1-2\n item 1-2\n paragraph 1-1\n list 2-2\n item 2-2\n paragraph 2-2" + }, + { + "source": "10) foo\n - bar\n", + "expected": "document 1-2\n list 1-1\n item 1-1\n paragraph 1-1\n list 2-2\n item 2-2\n paragraph 2-2" + }, + { + "source": "- - foo\n", + "expected": "document 1-1\n list 1-1\n item 1-1\n list 1-1\n item 1-1\n paragraph 1-1" + }, + { + "source": "1. - 2. foo\n", + "expected": "document 1-1\n list 1-1\n item 1-1\n list 1-1\n item 1-1\n list 1-1\n item 1-1\n paragraph 1-1" + }, + { + "source": "- # Foo\n- Bar\n ---\n baz\n", + "expected": "document 1-4\n list 1-4\n item 1-1\n heading 1-1\n item 2-4\n heading 2-3\n paragraph 4-4" + }, + { + "source": "- foo\n- bar\n+ baz\n", + "expected": "document 1-3\n list 1-2\n item 1-1\n paragraph 1-1\n item 2-2\n paragraph 2-2\n list 3-3\n item 3-3\n paragraph 3-3" + }, + { + "source": "1. foo\n2. bar\n3) baz\n", + "expected": "document 1-3\n list 1-2\n item 1-1\n paragraph 1-1\n item 2-2\n paragraph 2-2\n list 3-3\n item 3-3\n paragraph 3-3" + }, + { + "source": "Foo\n- bar\n- baz\n", + "expected": "document 1-3\n paragraph 1-1\n list 2-3\n item 2-2\n paragraph 2-2\n item 3-3\n paragraph 3-3" + }, + { + "source": "The number of windows in my house is\n14. The number of doors is 6.\n", + "expected": "document 1-2\n paragraph 1-2" + }, + { + "source": "The number of windows in my house is\n1. The number of doors is 6.\n", + "expected": "document 1-2\n paragraph 1-1\n list 2-2\n item 2-2\n paragraph 2-2" + }, + { + "source": "- foo\n\n- bar\n\n\n- baz\n", + "expected": "document 1-6\n list 1-6\n item 1-1\n paragraph 1-1\n item 3-3\n paragraph 3-3\n item 6-6\n paragraph 6-6" + }, + { + "source": "- foo\n - bar\n - baz\n\n\n bim\n", + "expected": "document 1-6\n list 1-6\n item 1-6\n paragraph 1-1\n list 2-6\n item 2-6\n paragraph 2-2\n list 3-6\n item 3-6\n paragraph 3-3\n paragraph 6-6" + }, + { + "source": "- foo\n- bar\n\n\n\n- baz\n- bim\n", + "expected": "document 1-7\n list 1-2\n item 1-1\n paragraph 1-1\n item 2-2\n paragraph 2-2\n html_block 4-4\n list 6-7\n item 6-6\n paragraph 6-6\n item 7-7\n paragraph 7-7" + }, + { + "source": "- foo\n\n notcode\n\n- foo\n\n\n\n code\n", + "expected": "document 1-9\n list 1-5\n item 1-3\n paragraph 1-1\n paragraph 3-3\n item 5-5\n paragraph 5-5\n html_block 7-7\n code_block 9-9" + }, + { + "source": "- a\n - b\n - c\n - d\n - e\n - f\n- g\n", + "expected": "document 1-7\n list 1-7\n item 1-1\n paragraph 1-1\n item 2-2\n paragraph 2-2\n item 3-3\n paragraph 3-3\n item 4-4\n paragraph 4-4\n item 5-5\n paragraph 5-5\n item 6-6\n paragraph 6-6\n item 7-7\n paragraph 7-7" + }, + { + "source": "1. a\n\n 2. b\n\n 3. c\n", + "expected": "document 1-5\n list 1-5\n item 1-1\n paragraph 1-1\n item 3-3\n paragraph 3-3\n item 5-5\n paragraph 5-5" + }, + { + "source": "- a\n - b\n - c\n - d\n - e\n", + "expected": "document 1-5\n list 1-5\n item 1-1\n paragraph 1-1\n item 2-2\n paragraph 2-2\n item 3-3\n paragraph 3-3\n item 4-5\n paragraph 4-5" + }, + { + "source": "1. a\n\n 2. b\n\n 3. c\n", + "expected": "document 1-5\n list 1-3\n item 1-1\n paragraph 1-1\n item 3-3\n paragraph 3-3\n code_block 5-5" + }, + { + "source": "- a\n- b\n\n- c\n", + "expected": "document 1-4\n list 1-4\n item 1-1\n paragraph 1-1\n item 2-2\n paragraph 2-2\n item 4-4\n paragraph 4-4" + }, + { + "source": "* a\n*\n\n* c\n", + "expected": "document 1-4\n list 1-4\n item 1-1\n paragraph 1-1\n item 2-2\n item 4-4\n paragraph 4-4" + }, + { + "source": "- a\n- b\n\n c\n- d\n", + "expected": "document 1-5\n list 1-5\n item 1-1\n paragraph 1-1\n item 2-4\n paragraph 2-2\n paragraph 4-4\n item 5-5\n paragraph 5-5" + }, + { + "source": "- a\n- b\n\n [ref]: /url\n- d\n", + "expected": "document 1-5\n list 1-5\n item 1-1\n paragraph 1-1\n item 2-4\n paragraph 2-2\n item 5-5\n paragraph 5-5" + }, + { + "source": "- a\n- ```\n b\n\n\n ```\n- c\n", + "expected": "document 1-7\n list 1-7\n item 1-1\n paragraph 1-1\n item 2-6\n code_block 2-6\n item 7-7\n paragraph 7-7" + }, + { + "source": "- a\n - b\n\n c\n- d\n", + "expected": "document 1-5\n list 1-5\n item 1-4\n paragraph 1-1\n list 2-4\n item 2-4\n paragraph 2-2\n paragraph 4-4\n item 5-5\n paragraph 5-5" + }, + { + "source": "* a\n > b\n >\n* c\n", + "expected": "document 1-4\n list 1-4\n item 1-3\n paragraph 1-1\n block_quote 2-3\n paragraph 2-2\n item 4-4\n paragraph 4-4" + }, + { + "source": "- a\n > b\n ```\n c\n ```\n- d\n", + "expected": "document 1-6\n list 1-6\n item 1-5\n paragraph 1-1\n block_quote 2-2\n paragraph 2-2\n code_block 3-5\n item 6-6\n paragraph 6-6" + }, + { + "source": "- a\n", + "expected": "document 1-1\n list 1-1\n item 1-1\n paragraph 1-1" + }, + { + "source": "- a\n - b\n", + "expected": "document 1-2\n list 1-2\n item 1-2\n paragraph 1-1\n list 2-2\n item 2-2\n paragraph 2-2" + }, + { + "source": "1. ```\n foo\n ```\n\n bar\n", + "expected": "document 1-5\n list 1-5\n item 1-5\n code_block 1-3\n paragraph 5-5" + }, + { + "source": "* foo\n * bar\n\n baz\n", + "expected": "document 1-4\n list 1-4\n item 1-4\n paragraph 1-1\n list 2-2\n item 2-2\n paragraph 2-2\n paragraph 4-4" + }, + { + "source": "- a\n - b\n - c\n\n- d\n - e\n - f\n", + "expected": "document 1-7\n list 1-7\n item 1-3\n paragraph 1-1\n list 2-3\n item 2-2\n paragraph 2-2\n item 3-3\n paragraph 3-3\n item 5-7\n paragraph 5-5\n list 6-7\n item 6-6\n paragraph 6-6\n item 7-7\n paragraph 7-7" + }, + { + "source": "`hi`lo`\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "`foo`\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "`` foo ` bar ``\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "` `` `\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "` `` `\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "` a`\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "` b `\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "` `\n` `\n", + "expected": "document 1-2\n paragraph 1-2" + }, + { + "source": "``\nfoo\nbar \nbaz\n``\n", + "expected": "document 1-5\n paragraph 1-5" + }, + { + "source": "``\nfoo \n``\n", + "expected": "document 1-3\n paragraph 1-3" + }, + { + "source": "`foo bar \nbaz`\n", + "expected": "document 1-2\n paragraph 1-2" + }, + { + "source": "`foo\\`bar`\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "``foo`bar``\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "` foo `` bar `\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "*foo`*`\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "[not a `link](/foo`)\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "`
`\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "`\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "``\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "`\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "```foo``\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "`foo\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "`foo``bar``\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "*foo bar*\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "a * foo bar*\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "a*\"foo\"*\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "* a *\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "foo*bar*\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "5*6*78\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "_foo bar_\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "_ foo bar_\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "a_\"foo\"_\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "foo_bar_\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "5_6_78\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "пристаням_стремятся_\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "aa_\"bb\"_cc\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "foo-_(bar)_\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "_foo*\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "*foo bar *\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "*foo bar\n*\n", + "expected": "document 1-2\n paragraph 1-2" + }, + { + "source": "*(*foo)\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "*(*foo*)*\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "*foo*bar\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "_foo bar _\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "_(_foo)\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "_(_foo_)_\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "_foo_bar\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "_пристаням_стремятся\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "_foo_bar_baz_\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "_(bar)_.\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "**foo bar**\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "** foo bar**\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "a**\"foo\"**\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "foo**bar**\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "__foo bar__\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "__ foo bar__\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "__\nfoo bar__\n", + "expected": "document 1-2\n paragraph 1-2" + }, + { + "source": "a__\"foo\"__\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "foo__bar__\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "5__6__78\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "пристаням__стремятся__\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "__foo, __bar__, baz__\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "foo-__(bar)__\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "**foo bar **\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "**(**foo)\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "*(**foo**)*\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "**Gomphocarpus (*Gomphocarpus physocarpus*, syn.\n*Asclepias physocarpa*)**\n", + "expected": "document 1-2\n paragraph 1-2" + }, + { + "source": "**foo \"*bar*\" foo**\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "**foo**bar\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "__foo bar __\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "__(__foo)\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "_(__foo__)_\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "__foo__bar\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "__пристаням__стремятся\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "__foo__bar__baz__\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "__(bar)__.\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "*foo [bar](/url)*\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "*foo\nbar*\n", + "expected": "document 1-2\n paragraph 1-2" + }, + { + "source": "_foo __bar__ baz_\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "_foo _bar_ baz_\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "__foo_ bar_\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "*foo *bar**\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "*foo **bar** baz*\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "*foo**bar**baz*\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "*foo**bar*\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "***foo** bar*\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "*foo **bar***\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "*foo**bar***\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "foo***bar***baz\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "foo******bar*********baz\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "*foo **bar *baz* bim** bop*\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "*foo [*bar*](/url)*\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "** is not an empty emphasis\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "**** is not an empty strong emphasis\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "**foo [bar](/url)**\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "**foo\nbar**\n", + "expected": "document 1-2\n paragraph 1-2" + }, + { + "source": "__foo _bar_ baz__\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "__foo __bar__ baz__\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "____foo__ bar__\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "**foo **bar****\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "**foo *bar* baz**\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "**foo*bar*baz**\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "***foo* bar**\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "**foo *bar***\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "**foo *bar **baz**\nbim* bop**\n", + "expected": "document 1-2\n paragraph 1-2" + }, + { + "source": "**foo [*bar*](/url)**\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "__ is not an empty emphasis\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "____ is not an empty strong emphasis\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "foo ***\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "foo *\\**\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "foo *_*\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "foo *****\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "foo **\\***\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "foo **_**\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "**foo*\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "*foo**\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "***foo**\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "****foo*\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "**foo***\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "*foo****\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "foo ___\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "foo _\\__\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "foo _*_\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "foo _____\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "foo __\\___\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "foo __*__\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "__foo_\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "_foo__\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "___foo__\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "____foo_\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "__foo___\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "_foo____\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "**foo**\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "*_foo_*\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "__foo__\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "_*foo*_\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "****foo****\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "____foo____\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "******foo******\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "***foo***\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "_____foo_____\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "*foo _bar* baz_\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "*foo __bar *baz bim__ bam*\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "**foo **bar baz**\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "*foo *bar baz*\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "*[bar*](/url)\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "_foo [bar_](/url)\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "*\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "**\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "__\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "*a `*`*\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "_a `_`_\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "**a\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "__a\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "[link](/uri \"title\")\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "[link](/uri)\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "[](./target.md)\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "[link]()\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "[link](<>)\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "[]()\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "[link](/my uri)\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "[link]()\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "[link](foo\nbar)\n", + "expected": "document 1-2\n paragraph 1-2" + }, + { + "source": "[link]()\n", + "expected": "document 1-2\n paragraph 1-2" + }, + { + "source": "[a]()\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "[link]()\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "[a](\n[a](c)\n", + "expected": "document 1-3\n paragraph 1-3" + }, + { + "source": "[link](\\(foo\\))\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "[link](foo(and(bar)))\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "[link](foo(and(bar))\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "[link](foo\\(and\\(bar\\))\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "[link]()\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "[link](foo\\)\\:)\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "[link](#fragment)\n\n[link](http://example.com#fragment)\n\n[link](http://example.com?foo=3#frag)\n", + "expected": "document 1-5\n paragraph 1-1\n paragraph 3-3\n paragraph 5-5" + }, + { + "source": "[link](foo\\bar)\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "[link](foo%20bä)\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "[link](\"title\")\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "[link](/url \"title\")\n[link](/url 'title')\n[link](/url (title))\n", + "expected": "document 1-3\n paragraph 1-3" + }, + { + "source": "[link](/url \"title \\\""\")\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "[link](/url \"title\")\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "[link](/url \"title \"and\" title\")\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "[link](/url 'title \"and\" title')\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "[link]( /uri\n \"title\" )\n", + "expected": "document 1-2\n paragraph 1-2" + }, + { + "source": "[link] (/uri)\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "[link [foo [bar]]](/uri)\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "[link] bar](/uri)\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "[link [bar](/uri)\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "[link \\[bar](/uri)\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "[link *foo **bar** `#`*](/uri)\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "[![moon](moon.jpg)](/uri)\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "[foo [bar](/uri)](/uri)\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "[foo *[bar [baz](/uri)](/uri)*](/uri)\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "![[[foo](uri1)](uri2)](uri3)\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "*[foo*](/uri)\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "[foo *bar](baz*)\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "*foo [bar* baz]\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "[foo \n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "[foo`](/uri)`\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "[foo\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "[foo][bar]\n\n[bar]: /url \"title\"\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "[link [foo [bar]]][ref]\n\n[ref]: /uri\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "[link \\[bar][ref]\n\n[ref]: /uri\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "[link *foo **bar** `#`*][ref]\n\n[ref]: /uri\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "[![moon](moon.jpg)][ref]\n\n[ref]: /uri\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "[foo [bar](/uri)][ref]\n\n[ref]: /uri\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "[foo *bar [baz][ref]*][ref]\n\n[ref]: /uri\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "*[foo*][ref]\n\n[ref]: /uri\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "[foo *bar][ref]*\n\n[ref]: /uri\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "[foo \n\n[ref]: /uri\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "[foo`][ref]`\n\n[ref]: /uri\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "[foo\n\n[ref]: /uri\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "[foo][BaR]\n\n[bar]: /url \"title\"\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "[ẞ]\n\n[SS]: /url\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "[Foo\n bar]: /url\n\n[Baz][Foo bar]\n", + "expected": "document 1-4\n paragraph 4-4" + }, + { + "source": "[foo] [bar]\n\n[bar]: /url \"title\"\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "[foo]\n[bar]\n\n[bar]: /url \"title\"\n", + "expected": "document 1-4\n paragraph 1-2" + }, + { + "source": "[foo]: /url1\n\n[foo]: /url2\n\n[bar][foo]\n", + "expected": "document 1-5\n paragraph 5-5" + }, + { + "source": "[bar][foo\\!]\n\n[foo!]: /url\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "[foo][ref[]\n\n[ref[]: /uri\n", + "expected": "document 1-3\n paragraph 1-1\n paragraph 3-3" + }, + { + "source": "[foo][ref[bar]]\n\n[ref[bar]]: /uri\n", + "expected": "document 1-3\n paragraph 1-1\n paragraph 3-3" + }, + { + "source": "[[[foo]]]\n\n[[[foo]]]: /url\n", + "expected": "document 1-3\n paragraph 1-1\n paragraph 3-3" + }, + { + "source": "[foo][ref\\[]\n\n[ref\\[]: /uri\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "[bar\\\\]: /uri\n\n[bar\\\\]\n", + "expected": "document 1-3\n paragraph 3-3" + }, + { + "source": "[]\n\n[]: /uri\n", + "expected": "document 1-3\n paragraph 1-1\n paragraph 3-3" + }, + { + "source": "[\n ]\n\n[\n ]: /uri\n", + "expected": "document 1-5\n paragraph 1-2\n paragraph 4-5" + }, + { + "source": "[foo][]\n\n[foo]: /url \"title\"\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "[*foo* bar][]\n\n[*foo* bar]: /url \"title\"\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "[Foo][]\n\n[foo]: /url \"title\"\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "[foo] \n[]\n\n[foo]: /url \"title\"\n", + "expected": "document 1-4\n paragraph 1-2" + }, + { + "source": "[foo]\n\n[foo]: /url \"title\"\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "[*foo* bar]\n\n[*foo* bar]: /url \"title\"\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "[[*foo* bar]]\n\n[*foo* bar]: /url \"title\"\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "[[bar [foo]\n\n[foo]: /url\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "[Foo]\n\n[foo]: /url \"title\"\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "[foo] bar\n\n[foo]: /url\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "\\[foo]\n\n[foo]: /url \"title\"\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "[foo*]: /url\n\n*[foo*]\n", + "expected": "document 1-3\n paragraph 3-3" + }, + { + "source": "[foo][bar]\n\n[foo]: /url1\n[bar]: /url2\n", + "expected": "document 1-4\n paragraph 1-1" + }, + { + "source": "[foo][]\n\n[foo]: /url1\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "[foo]()\n\n[foo]: /url1\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "[foo](not a link)\n\n[foo]: /url1\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "[foo][bar][baz]\n\n[baz]: /url\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "[foo][bar][baz]\n\n[baz]: /url1\n[bar]: /url2\n", + "expected": "document 1-4\n paragraph 1-1" + }, + { + "source": "[foo][bar][baz]\n\n[baz]: /url1\n[foo]: /url2\n", + "expected": "document 1-4\n paragraph 1-1" + }, + { + "source": "![foo](/url \"title\")\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "![foo *bar*]\n\n[foo *bar*]: train.jpg \"train & tracks\"\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "![foo ![bar](/url)](/url2)\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "![foo [bar](/url)](/url2)\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "![foo *bar*][]\n\n[foo *bar*]: train.jpg \"train & tracks\"\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "![foo *bar*][foobar]\n\n[FOOBAR]: train.jpg \"train & tracks\"\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "![foo](train.jpg)\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "My ![foo bar](/path/to/train.jpg \"title\" )\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "![foo]()\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "![](/url)\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "![foo][bar]\n\n[bar]: /url\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "![foo][bar]\n\n[BAR]: /url\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "![foo][]\n\n[foo]: /url \"title\"\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "![*foo* bar][]\n\n[*foo* bar]: /url \"title\"\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "![Foo][]\n\n[foo]: /url \"title\"\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "![foo] \n[]\n\n[foo]: /url \"title\"\n", + "expected": "document 1-4\n paragraph 1-2" + }, + { + "source": "![foo]\n\n[foo]: /url \"title\"\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "![*foo* bar]\n\n[*foo* bar]: /url \"title\"\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "![[foo]]\n\n[[foo]]: /url \"title\"\n", + "expected": "document 1-3\n paragraph 1-1\n paragraph 3-3" + }, + { + "source": "![Foo]\n\n[foo]: /url \"title\"\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "!\\[foo]\n\n[foo]: /url \"title\"\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "\\![foo]\n\n[foo]: /url \"title\"\n", + "expected": "document 1-3\n paragraph 1-1" + }, + { + "source": "\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "<>\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "< http://foo.bar >\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "http://example.com\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "foo@bar.example.com\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "\n", + "expected": "document 1-2\n paragraph 1-2" + }, + { + "source": "\n", + "expected": "document 1-2\n paragraph 1-2" + }, + { + "source": "Foo \n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "<33> <__>\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": " \n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "< a><\nfoo>\n\n", + "expected": "document 1-4\n paragraph 1-4" + }, + { + "source": "\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "foo \n", + "expected": "document 1-2\n paragraph 1-2" + }, + { + "source": "foo foo -->\n\nfoo foo -->\n", + "expected": "document 1-3\n paragraph 1-1\n paragraph 3-3" + }, + { + "source": "foo \n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "foo \n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "foo &<]]>\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "foo \n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "foo \n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "foo \nbaz\n", + "expected": "document 1-2\n paragraph 1-2" + }, + { + "source": "foo\\\nbaz\n", + "expected": "document 1-2\n paragraph 1-2" + }, + { + "source": "foo \nbaz\n", + "expected": "document 1-2\n paragraph 1-2" + }, + { + "source": "foo \n bar\n", + "expected": "document 1-2\n paragraph 1-2" + }, + { + "source": "foo\\\n bar\n", + "expected": "document 1-2\n paragraph 1-2" + }, + { + "source": "*foo \nbar*\n", + "expected": "document 1-2\n paragraph 1-2" + }, + { + "source": "*foo\\\nbar*\n", + "expected": "document 1-2\n paragraph 1-2" + }, + { + "source": "`code \nspan`\n", + "expected": "document 1-2\n paragraph 1-2" + }, + { + "source": "`code\\\nspan`\n", + "expected": "document 1-2\n paragraph 1-2" + }, + { + "source": "\n", + "expected": "document 1-2\n paragraph 1-2" + }, + { + "source": "\n", + "expected": "document 1-2\n paragraph 1-2" + }, + { + "source": "foo\\\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "foo \n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "### foo\\\n", + "expected": "document 1-1\n heading 1-1" + }, + { + "source": "### foo \n", + "expected": "document 1-1\n heading 1-1" + }, + { + "source": "foo\nbaz\n", + "expected": "document 1-2\n paragraph 1-2" + }, + { + "source": "foo \n baz\n", + "expected": "document 1-2\n paragraph 1-2" + }, + { + "source": "hello $.;'there\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "Foo χρῆν\n", + "expected": "document 1-1\n paragraph 1-1" + }, + { + "source": "Multiple spaces\n", + "expected": "document 1-1\n paragraph 1-1" + } + ] +} diff --git a/test/fill/code.kt b/test/fill/code.kt new file mode 100644 index 0000000..d6d2236 --- /dev/null +++ b/test/fill/code.kt @@ -0,0 +1,5 @@ +public class Foo { + fun foo() { + println("Code should not affected by fill-region") + } +} diff --git a/test/fill/comment.kt b/test/fill/comment.kt new file mode 100644 index 0000000..28a1a8d --- /dev/null +++ b/test/fill/comment.kt @@ -0,0 +1,153 @@ +// kotlin-mode--test-paragraph +public class Foo { + // Different level of comments. + + // kotlin-mode--test-paragraph + // + // kotlin-mode--test-paragraph + /// kotlin-mode--test-paragraph + /// + /// kotlin-mode--test-paragraph + // kotlin-mode--test-paragraph + // + // kotlin-mode--test-paragraph + + // Comment box: + + /////////////////////// + // kotlin-mode--test-paragraph + // + // kotlin-mode--test-paragraph + /////////////////////// + + /////////////////////// + /////////////////////// + // + // kotlin-mode--test-paragraph + // + // kotlin-mode--test-paragraph + // + /////////////////////// + /////////////////////// + + /********************** + * kotlin-mode--test-paragraph + * + * kotlin-mode--test-paragraph + *********************/ + + + // Different level of comments, with comment box. + + // kotlin-mode--test-paragraph + // + // kotlin-mode--test-paragraph + //////////////////////////// + /// kotlin-mode--test-paragraph + /// + /// kotlin-mode--test-paragraph + //////////////////////////// + // kotlin-mode--test-paragraph + // + // kotlin-mode--test-paragraph + + /* + * kotlin-mode--test-paragraph + * + * kotlin-mode--test-paragraph + */ + /** + * kotlin-mode--test-paragraph + * + * kotlin-mode--test-paragraph + */ + /* + * kotlin-mode--test-paragraph + * + * kotlin-mode--test-paragraph + */ + + + // Code and comments + + /// kotlin-mode--test-paragraph + fun foo() {} + + /// kotlin-mode--test-paragraph + fun foo() {} + + /** + * kotlin-mode--test-paragraph + */ + fun foo() {} + + /** + * kotlin-mode--test-paragraph + */ + fun foo() {} + + + // CommonMark + + // kotlin-mode--test-paragraph + // ## aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa + // - kotlin-mode--test-paragraph + // - kotlin-mode--test-paragraph + // - kotlin-mode--test-paragraph + // ``` + // aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa + // ``` + // kotlin-mode--test-paragraph + // - - - + // kotlin-mode--test-paragraph + // + // > kotlin-mode--test-paragraph + // > + // > kotlin-mode--test-paragraph + // + // aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa + // + // * >> - >10. 2) kotlin-mode--test-paragraph + // >> - >10. 2) kotlin-mode--test-paragraph + // >> > + // >> > kotlin-mode--test-paragraph + + /** + * kotlin-mode--test-paragraph + * ## aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa + * - kotlin-mode--test-paragraph + * - kotlin-mode--test-paragraph + * - kotlin-mode--test-paragraph + * ``` + * aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa + * ``` + * kotlin-mode--test-paragraph + * - - - + * kotlin-mode--test-paragraph + * + * > kotlin-mode--test-paragraph + * > + * > kotlin-mode--test-paragraph + * + * aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa + * + * * >> - >10. 2) kotlin-mode--test-paragraph + * >> - >10. 2) kotlin-mode--test-paragraph + * >> > + * >> > kotlin-mode--test-paragraph + * + * @param aaa kotlin-mode--test-paragraph + * @param aaa kotlin-mode--test-paragraph + * @param aaa + * kotlin-mode--test-paragraph + * @return kotlin-mode--test-paragraph + */ + + // Strings + """ +kotlin-mode--test-paragraph + +kotlin-mode--test-paragraph + """ +} +// kotlin-mode--test-paragraph diff --git a/test/kotlin-mode-fill-test.el b/test/kotlin-mode-fill-test.el new file mode 100644 index 0000000..496665b --- /dev/null +++ b/test/kotlin-mode-fill-test.el @@ -0,0 +1,638 @@ +;;; kotlin-mode-fill-test.el --- Test for kotlin-mode: filling -*- lexical-binding: t -*- +;; Copyright (C) 2016, 2022, 2023 taku0, Josh Caswell + +;; Authors: taku0 (https://github.com/taku0) +;; Josh Caswell (https://github.com/woolsweater) + +;; This file is not part of GNU Emacs. + +;; This program 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 program 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 program. If not, see . + +;;; Commentary: + +;; Test for kotlin-mode: paragraph fill +;; +;; Strategy: +;; +;; 1. Make sure `fill-region-as-paragraph' is correct by manually crafted test +;; cases. +;; +;; 2. Generate test cases for `fill-region' and `fill-paragraph' using +;; `fill-region-as-paragraph' and template files. + +;;; Code: + +(require 'kotlin-mode) +(require 'kotlin-mode-fill) +(require 'ert) + +(defvar kotlin-mode--test-directory + (file-name-directory + (if (fboundp 'macroexp-file-name) (macroexp-file-name) + (or load-file-name buffer-file-name)))) + +(defun kotlin-mode--test-fill-region-as-paragraph + (input + expected + fill-column-for-test) + "Run a test for `fill-region-as-paragraph'. + +INPUT is a text before filling. Characters \"{\" and \"}\" represents the +region and removed before filling. + +EXPECTED is the expected result. + +FILL-COLUMN-FOR-TEST is used for `fill-column'." + (with-temp-buffer + (switch-to-buffer (current-buffer)) + (insert input) + (kotlin-mode) + (syntax-propertize (point-max)) + (let ((fill-column fill-column-for-test) + start + end) + (goto-char (point-min)) + (search-forward "{") + (delete-char -1) + (setq start (point)) + (search-forward "}") + (delete-char -1) + (setq end (point)) + (fill-region-as-paragraph start end)) + (should (string-equal + (buffer-substring-no-properties (point-min) (point-max)) + expected)))) + +(ert-deftest fill-region-as-paragraph--sinle-line-comments--insert-breaks () + (kotlin-mode--test-fill-region-as-paragraph + ;; Input + " +/// {abc def ghi} +" + ;; Expected + " +/// abc def +/// ghi +" + 12)) + +(ert-deftest fill-region-as-paragraph--sinle-line-comments--delete-breaks () + (kotlin-mode--test-fill-region-as-paragraph + ;; Input + " +/// {abc +/// def +/// ghi} +" + ;; Expected + " +/// abc def +/// ghi +" + 12)) + +(ert-deftest fill-region-as-paragraph--multiline-comments--insert-breaks () + (kotlin-mode--test-fill-region-as-paragraph + ;; Input + " +/* {abc def ghi} */ +" + ;; Expected + " +/* + * abc def + * ghi + */ +" + 12)) + +(ert-deftest fill-region-as-paragraph--multiline-comments--delete-breaks () + (kotlin-mode--test-fill-region-as-paragraph + ;; Input + " +/* + * {abc + * def + * ghi} + */ +" + ;; Expected + " +/* + * abc def + * ghi + */ +" + 12)) + +(ert-deftest fill-region-as-paragraph--multiline-comments--to-one-line () + (kotlin-mode--test-fill-region-as-paragraph + ;; Input + " +/* + * {abc + * def + * ghi} + */ +" + ;; Expected + " +/* abc def ghi */ +" + 80)) + +(ert-deftest fill-region-as-paragraph--keep-line-break-after-open-delimiter () + (kotlin-mode--test-fill-region-as-paragraph + ;; Input + " +/* + * {abc + * def + * ghi} */ +" + ;; Expected + " +/* + * abc def + * ghi */ +" + 12)) + +(ert-deftest fill-region-as-paragraph--keep-line-break-before-close-delimiter () + (kotlin-mode--test-fill-region-as-paragraph + ;; Input + " +/* {abc + * def + * ghi} + */ +" + ;; Expected + " +/* abc def + * ghi + */ +" + 12)) + +(ert-deftest fill-region-as-paragraph--block-quote--insert-breaks () + (kotlin-mode--test-fill-region-as-paragraph + ;; Input + " +/// > {abc def ghi} +" + ;; Expected + " +/// > abc def +/// > ghi +" + 16)) + +(ert-deftest fill-region-as-paragraph--block-quote--delete-breaks () + (kotlin-mode--test-fill-region-as-paragraph + ;; Input + " +/// > {abc +/// > def +/// > ghi} +" + ;; Expected + " +/// > abc def +/// > ghi +" + 16)) + +(ert-deftest fill-region-as-paragraph--unordered-list-hyphen--insert-breaks () + (kotlin-mode--test-fill-region-as-paragraph + ;; Input + " +/// - {abc def ghi} +" + ;; Expected + " +/// - abc def +/// ghi +" + 16)) + +(ert-deftest fill-region-as-paragraph--unordered-list-hyphen--delete-breaks () + (kotlin-mode--test-fill-region-as-paragraph + ;; Input + " +/// - {abc +/// def +/// ghi} +" + ;; Expected + " +/// - abc def +/// ghi +" + 16)) + +(ert-deftest fill-region-as-paragraph--unordered-list-plus--insert-breaks () + (kotlin-mode--test-fill-region-as-paragraph + ;; Input + " +/// + {abc def ghi} +" + ;; Expected + " +/// + abc def +/// ghi +" + 16)) + +(ert-deftest fill-region-as-paragraph--unordered-list-plus--delete-breaks () + (kotlin-mode--test-fill-region-as-paragraph + ;; Input + " +/// + {abc +/// def +/// ghi} +" + ;; Expected + " +/// + abc def +/// ghi +" + 16)) + +(ert-deftest fill-region-as-paragraph--unordered-list-asterisk--insert-breaks () + (kotlin-mode--test-fill-region-as-paragraph + ;; Input + " +/* + * * {abc def ghi} + */ +" + ;; Expected + " +/* + * * abc def + * ghi + */ +" + 12)) + +(ert-deftest fill-region-as-paragraph--unordered-list-asterisk--delete-breaks () + (kotlin-mode--test-fill-region-as-paragraph + ;; Input + " +/* + * * {abc + * def + * ghi} + */ +" + ;; Expected + " +/* + * * abc def + * ghi + */ +" + 12)) + +(ert-deftest fill-region-as-paragraph--ordered-list-dot--insert-breaks () + (kotlin-mode--test-fill-region-as-paragraph + ;; Input + " +/// 1. {abc def ghi} +" + ;; Expected + " +/// 1. abc def +/// ghi +" + 16)) + +(ert-deftest fill-region-as-paragraph--ordered-list-dot--delete-breaks () + (kotlin-mode--test-fill-region-as-paragraph + ;; Input + " +/// 1. {abc +/// def +/// ghi} +" + ;; Expected + " +/// 1. abc def +/// ghi +" + 16)) + +(ert-deftest fill-region-as-paragraph--ordered-list-paren--insert-breaks () + (kotlin-mode--test-fill-region-as-paragraph + ;; Input + " +/// 1) {abc def ghi} +" + ;; Expected + " +/// 1) abc def +/// ghi +" + 16)) + +(ert-deftest fill-region-as-paragraph--ordered-list-paren--delete-breaks () + (kotlin-mode--test-fill-region-as-paragraph + ;; Input + " +/// 1) {abc +/// def +/// ghi} +" + ;; Expected + " +/// 1) abc def +/// ghi +" + 16)) + +(ert-deftest fill-region-as-paragraph--comment-tag--insert-breaks () + (kotlin-mode--test-fill-region-as-paragraph + ;; Input + " +/** + * @param test {abc def ghi} + */ +" + ;; Expected + " +/** + * @param test abc + * def ghi + */ +" + 16)) + +(ert-deftest fill-region-as-paragraph--comment-tag--delete-breaks () + (kotlin-mode--test-fill-region-as-paragraph + ;; Input + " +/** + * @param test {abc + * def + * ghi} + */ +" + ;; Expected + " +/** + * @param test abc + * def ghi + */ +" + 16)) + +(ert-deftest fill-region-as-paragraph--complex-prefix--insert-breaks () + (kotlin-mode--test-fill-region-as-paragraph + ;; Input + " +// * >> - >10. 2) {abc def ghi} +" + ;; Expected + " +// * >> - >10. 2) abc def +// >> > ghi +" + 32)) + +(ert-deftest fill-region-as-paragraph--complex-prefix--delete-breaks () + (kotlin-mode--test-fill-region-as-paragraph + ;; Input + " +// * >> - >10. 2) {abc +// >> > def +// >> > ghi} +" + ;; Expected + " +// * >> - >10. 2) abc def +// >> > ghi +" + 32)) + +(defun kotlin-mode--parse-fill-test () + "Parse the current buffer as a test file and return its structure. + +The result is list of elements, which is one of: + +- non-paragraph line, (literal STRING), where STRING is the line excluding a + line break, + +- paragraph, (paragraph PREFIX), where PREFIX is the prefix before the + paragraph." + (save-excursion + (goto-char (point-min)) + (let ((result ())) + (while (not (eobp)) + (push (if (looking-at "\\(.*\\)kotlin-mode--test-paragraph") + (list 'paragraph (match-string-no-properties 1)) + (list 'literal + (buffer-substring-no-properties + (line-beginning-position) + (line-end-position)))) + result) + (forward-line)) + (reverse result)))) + +(defun kotlin-mode--test-fill-region (lines mode) + "Define tests for `fill-region'. + +LINES is the parsed lines of the test file. +See `kotlin-mode--parse-fill-test' for details. + +MODE is either `break' or `join'. If it is `break', test breaking long lines. +If it is `join' test joining short lines." + (let (regions + expected + actual) + (with-temp-buffer + (switch-to-buffer (current-buffer)) + (kotlin-mode) + (setq regions (kotlin-mode--insert-fill-test-lines lines mode)) + (syntax-propertize (point-max)) + (dolist (region (reverse regions)) + (fill-region-as-paragraph (nth 0 region) (nth 1 region))) + (setq expected (buffer-substring-no-properties (point-min) (point-max)))) + (with-temp-buffer + (switch-to-buffer (current-buffer)) + (kotlin-mode) + (kotlin-mode--insert-fill-test-lines lines mode) + (syntax-propertize (point-max)) + (fill-region (point-min) (point-max)) + (setq actual (buffer-substring-no-properties (point-min) (point-max)))) + (should (string-equal expected actual)))) + +(defun kotlin-mode--test-fill-paragraph (lines mode) + "Run tests for `fill-paragraph'. + +LINES is the parsed lines of the test file. +See `kotlin-mode--parse-fill-test' for details. + +MODE is either `break' or `join'. If it is `break', test breaking long lines. +If it is `join' test joining short lines." + (let (regions + original + expected + (point-placements '(beginning-of-line + after-indent + beginning-of-region + end-of-line + end-of-region))) + (with-temp-buffer + (switch-to-buffer (current-buffer)) + (kotlin-mode) + (setq regions (kotlin-mode--insert-fill-test-lines lines mode)) + (setq original (buffer-string)) + (dolist (region regions) + (delete-region (point-min) (point-max)) + (insert original) + (syntax-propertize (point-max)) + (fill-region-as-paragraph (nth 0 region) (nth 1 region)) + (setq expected (buffer-substring-no-properties (point-min) (point-max))) + (dolist (point-placement point-placements) + (kotlin-mode--do-test-fill-paragraph + original + expected + region + point-placement)))))) + +(defun kotlin-mode--do-test-fill-paragraph (original + expected + region + point-placement) + "Run single test for `fill-paragraph'. + +ORIGINAL is a text before filling. + +EXPECTED is the expected result. + +REGION is the region of the paragraph. + +POINT-PLACEMENT designates where to put the point before filling. It must be +one of the following: +- `beginning-of-line' +- `after-indent' +- `beginning-of-region' +- `end-of-line' +- `end-of-region'" + (let (actual) + (delete-region (point-min) (point-max)) + (insert original) + (syntax-propertize (point-max)) + (cond + ((eq point-placement 'beginning-of-line) + (goto-char (nth 0 region)) + (forward-line 0)) + ((eq point-placement 'after-indent) + (goto-char (nth 0 region)) + (back-to-indentation)) + ((eq point-placement 'beginning-of-region) + (goto-char (nth 0 region))) + ((eq point-placement 'end-of-line) + (goto-char (nth 0 region)) + (end-of-line)) + ((eq point-placement 'end-of-region) + (goto-char (nth 1 region)))) + (fill-paragraph) + (setq actual (buffer-substring-no-properties (point-min) (point-max))) + (should (string-equal expected actual)))) + +(defun kotlin-mode--insert-fill-test-lines (lines mode) + "Insert parsed lines at point. + +LINES is the parsed lines of the test file. +See `kotlin-mode--parse-fill-test' for details. + +MODE is either `break' or `join'. If it is `break', test breaking long lines. +If it is `join' test joining short lines. + +Return regions of paragraphs" + (let (regions + start) + (dolist (line lines) + (cond + ((eq (car line) 'literal) + (insert (nth 1 line) "\n")) + ((eq (car line) 'paragraph) + (setq start (kotlin-mode--insert-fill-test-paragraph (nth 1 line) mode)) + (push (list start (1- (point))) regions)))) + (reverse regions))) + +(defun kotlin-mode--insert-fill-test-paragraph (prefix mode) + "Insert a test paragraph at point. + +PREFIX is inserted before the paragraph. + +If MODE is `join', insert short multiple lines. If MODE is `break', insert a +long line. + +Return point after prefix." + (let (start + column-after-first-word) + (insert prefix) + (setq start (point)) + (insert "aaa ") + (setq column-after-first-word (current-column)) + (dotimes (_ 100) + (insert "aaa ")) + (insert "\n") + (when (eq mode 'join) + (let ((fill-column column-after-first-word)) + (fill-region-as-paragraph start (1- (point))))) + start)) + +(defmacro kotlin-mode--define-fill-tests () + "Define paragraph fill tests for `kotlin-mode' from Kotlin files." + (let* ((basedir (file-name-directory kotlin-mode--test-directory)) + (default-directory (concat (file-name-as-directory basedir) "fill")) + (lines (make-symbol "lines")) + definitions) + (dolist (kotlin-file (file-expand-wildcards "*.kt")) + (dolist (mode '(break join)) + (dolist (test '(kotlin-mode--test-fill-region + kotlin-mode--test-fill-paragraph)) + (push `(ert-deftest ,(intern (concat + "fill-region--" + (symbol-name mode) + "--" + (file-name-base kotlin-file))) + () + (let* ((default-directory ,default-directory) + (,lines (with-temp-buffer + (switch-to-buffer (current-buffer)) + (insert-file-contents-literally + ,kotlin-file) + (let ((coding-system-for-read 'utf-8)) + (decode-coding-inserted-region + (point-min) + (point-max) + ,kotlin-file)) + (kotlin-mode) + (syntax-propertize (point-max)) + (kotlin-mode--parse-fill-test)))) + (,test ,lines ',mode))) + definitions)))) + (cons 'progn (reverse definitions)))) + +(eval + '(kotlin-mode--define-fill-tests)) + +(provide 'kotlin-mode-fill-test) + +;;; kotlin-mode-fill-test.el ends here diff --git a/test/kotlin-mode-kdoc-comment-parser-test.el b/test/kotlin-mode-kdoc-comment-parser-test.el new file mode 100644 index 0000000..da7d9cd --- /dev/null +++ b/test/kotlin-mode-kdoc-comment-parser-test.el @@ -0,0 +1,118 @@ +;;; kotlin-mode-kdoc-comment-parser-test.el --- Test for kotlin-mode: KDoc parser -*- lexical-binding: t -*- +;; Copyright (C) 2023 taku0 + +;; Authors: taku0 (https://github.com/taku0) + +;; This file is not part of GNU Emacs. + +;; This program 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 program 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 program. If not, see . + +;;; Commentary: + +;; Test for kotlin-mode: KDoc parser. + +;;; Code: + + +(require 'ert) +(require 'kotlin-mode-kdoc-comment-parser) +(require 'kotlin-mode-test) +(require 'json) +(require 'seq) + +(defvar kotlin-mode--test-directory + (file-name-directory + (if (fboundp 'macroexp-file-name) (macroexp-file-name) + (or load-file-name buffer-file-name)))) + +(ert-deftest kotlin-mode--test-kdoc-comment-parser () + "Test KDoc comment parser. + +Parse examples from CommonMark spec and compare results with parse result of +commonmark.js. + +Only node types and start/end lines are checked." + (let* ((default-directory (file-name-directory kotlin-mode--test-directory)) + (json-object-type 'hash-table) + (cases (gethash "cases" (json-read-file "commonmark_cases.json"))) + (replacements '(("indented-code-block" . "code_block") + ("fenced-code-block" . "code_block") + ("atx-heading" . "heading") + ("setext-heading" . "heading") + ("list-item" . "item") + ("block-quote" . "block_quote") + ("thematic-break" . "thematic_break"))) + tree + source + actual + expected) + (seq-doseq (case cases) + (with-temp-buffer + (setq tab-width 4) + (setq source (gethash "source" case)) + (insert source) + (setq tree (kotlin-mode--parse-kdoc-comment (point-min) (point-max))) + (setq actual (kotlin-mode--comment-node-dump tree)) + ;; `string-replace' is introduced at Emacs 28. + ;; (setq actual (string-replace (car replacement) + ;; (cdr replacement) + ;; actual)) + (with-temp-buffer + (insert actual) + (dolist (replacement replacements) + (goto-char (point-min)) + (while (search-forward (car replacement) nil t) + (replace-match (cdr replacement) nil t))) + (setq actual + (buffer-substring-no-properties (point-min) (point-max)))) + (setq expected (gethash "expected" case)) + (unless (or + ;; HTML blocks are not supported yet. + (string-match-p "html" expected) + ;; Link reference definitions are not supported yet. + (string-match-p "]:" source)) + (if (and kotlin-mode--test-keep-going + (not (string= actual expected))) + (message (concat "source:\n" + "````````````````````````````````\n" + source + "````````````````````````````````\n\n" + "expected:\n" + "````````````````````````````````\n" + expected + "\n````````````````````````````````\n\n" + "actual:\n" + "````````````````````````````````\n" + actual + "\n````````````````````````````````\n\n")) + (should (equal (concat + "source:\n" + "````````````````````````````````\n" + source + "````````````````````````````````\n\n" + "parsed:\n" + "````````````````````````````````\n" + expected) + (concat + "source:\n" + "````````````````````````````````\n" + source + "````````````````````````````````\n\n" + "parsed:\n" + "````````````````````````````````\n" + actual))))))))) + +(provide 'kotlin-mode-kdoc-comment-parser-test) + +;;; kotlin-mode-kdoc-comment-parser-test.el ends here diff --git a/test/kotlin-mode-test.el b/test/kotlin-mode-test.el index b6f1ebf..8b334fe 100644 --- a/test/kotlin-mode-test.el +++ b/test/kotlin-mode-test.el @@ -1,5 +1,10 @@ (require 'kotlin-mode) +(defvar kotlin-mode--test-directory + (file-name-directory + (if (fboundp 'macroexp-file-name) (macroexp-file-name) + (or load-file-name buffer-file-name)))) + (ert-deftest kotlin-mode--top-level-indent-test () (with-temp-buffer (let ((text "package com.gregghz.emacs @@ -184,25 +189,26 @@ println(arg) "If non-nil, do not stop at error in `kotlin-mode--sample-test'.") (ert-deftest kotlin-mode--sample-test () - (dolist (filename '("test/sample.kt" "test/pathological.kt")) - (with-temp-buffer - (insert-file-contents filename) - (goto-char (point-min)) - (kotlin-mode) - (setq-local indent-tabs-mode nil) - (setq-local tab-width 4) - (setq-local kotlin-tab-width 4) - - (while (not (eobp)) - (kotlin-mode--test-current-line filename nil) - - ;; Indent without following lines - (narrow-to-region (point-min) (line-end-position)) - (kotlin-mode--test-current-line filename t) - (widen) - - ;; Go to the next non-empty line - (next-non-empty-line))))) + (let ((default-directory (file-name-directory kotlin-mode--test-directory))) + (dolist (filename '("sample.kt" "pathological.kt")) + (with-temp-buffer + (insert-file-contents filename) + (goto-char (point-min)) + (kotlin-mode) + (setq-local indent-tabs-mode nil) + (setq-local tab-width 4) + (setq-local kotlin-tab-width 4) + + (while (not (eobp)) + (kotlin-mode--test-current-line filename nil) + + ;; Indent without following lines + (narrow-to-region (point-min) (line-end-position)) + (kotlin-mode--test-current-line filename t) + (widen) + + ;; Go to the next non-empty line + (next-non-empty-line)))))) (defun kotlin-mode--test-current-line (filename truncated) (back-to-indentation) @@ -271,3 +277,5 @@ println(arg) ;; Restore to original indentation for KNOWN_BUG line. (indent-line-to original-indent))) + +(provide 'kotlin-mode-test)