;;; scala-mode-paragraph.el - Major mode for editing scala, paragraph
;;; detection and fill
;;; Copyright (c) 2012 Heikki Vesalainen For information on the License,
;;; see the LICENSE file

;;; Based on Scala Language Specification (SLS) Version 2.9

;;; Provides paragraph navigation and fill for scaladocs and
;;; multi-line strings.

(defconst scala-paragraph:paragraph-line-start-re
  (concat "\\(?:\\s-*"                 ; whitespace
          "\\(?://+\\|\\*\\|/\\*+"     ; comment start
          "\\||\\)?"                   ; multi-line margin |
          "\\s-*\\)"))                 ; whitespace

(defconst scala-paragraph:scaladoc-list-start-re
  (concat "\\(?:-"                     ; unordered liststs
          "\\|[1IiAa]\\."              ; ordered lists
          "\\)\\s-*"))

(defconst scala-paragraph:fill-first-line-re
  (concat "\\s-*\\(//+\\|\\*\\||\\)?\\s-*"
          "\\(?:" scala-paragraph:scaladoc-list-start-re "\\)?"))

(defconst scala-paragraph:paragraph-start-re
  (concat scala-paragraph:paragraph-line-start-re
          "\\(?:$"                ; empty line
          "\\|==*[^=]+==*[ ]*$"   ; headings
          "\\|"
          scala-paragraph:scaladoc-list-start-re
          "\\|{{{"                ; code block start
          "\\|}}}"                ; code block end
          "\\|@[a-zA-Z]+\\>"      ; annotations
          "\\)"
          "\\|\\(?:\\s-*\\*/\\)"  ; end of comment
          ))

(defconst scala-paragraph:paragraph-separate-re
  (concat scala-paragraph:paragraph-line-start-re
          "\\(?:$\\)"
          "\\|\\(?:\\s *\\*/\\)"    ; end of comment
          ))

(defun scala-paragraph:fill-function ()
  (let (fill)
    (save-restriction
      (save-excursion
        (widen)
        (beginning-of-line)
        (cond ((looking-at "\\s-*/?\\*+\\s-*")
               (setq fill (replace-regexp-in-string
                           "/\\*+"
                           (lambda (str) (if (= (length str) 3) "  *" " *"))
                           (match-string-no-properties 0)))
               (goto-char (match-end 0))
               (when (looking-at scala-paragraph:scaladoc-list-start-re)
                 (setq fill
                       (concat fill (make-string (- (match-end 0)
                                                    (match-beginning 0)) ?\s)))))
              ((or (re-search-forward "\"\"\"|" (line-end-position) t)
                   (and (eq (nth 3 (syntax-ppss)) t)
                        (re-search-forward "^\\s-*|" (line-end-position) t)))
               (setq fill (concat (make-string (- (current-column) 1) ?\s) "|"))
               (setq fill (concat fill (make-string (skip-syntax-forward " ") ?\s)))
               (when (looking-at scala-paragraph:scaladoc-list-start-re)
                 (setq fill
                       (concat fill (make-string (- (match-end 0)
                                                    (match-beginning 0)) ?\s))))))))
    fill))

(defun scala-paragraph:fill-paragraph (&rest args)
  ;; move to inside multi-line comment or multi-line string, if outside
  (when (looking-at "\\s-*\\(?:/\\**\\|\"\"\"\\)\\s-*")
    (goto-char (match-end 0)))
  (let ((state (syntax-ppss))
        (fill-paragraph-function
         ;; Avoid infinite recursion, set fill-paragraph-function to
         ;; nil if it is 'scala-paragraph:fill-paragraph
         (unless (eq fill-paragraph-function 'scala-paragraph:fill-paragraph)
           fill-paragraph-function)))
    (cond ((integerp (nth 4 state))
           ;; mask multi-line comments and fill
           (save-restriction
             (narrow-to-region (nth 8 state)
                               (save-excursion (goto-char (nth 8 state))
                                               (if (forward-comment 1)
                                                   (point)
                                                 (point-max))))
             (apply #'fill-paragraph args))
           t)
          ((eq (nth 4 state) t)
           ;; line comment, let normal fill-function handle this
           nil)
          ((eq (nth 3 state) t)
           ;; mask multi-line strings and fill.
           (save-restriction
             (narrow-to-region (nth 8 state)
                               (save-excursion (goto-char (nth 8 state))
                                               (or (ignore-errors
                                                     (forward-sexp)
                                                     (point))
                                                   (point-max))))
             (apply #'fill-paragraph args))
           t)
          ;; TODO: fill lists
          ;; the rest should not be filled (code, etc)
          (t t))))

(provide 'scala-mode-paragraph)