;;; haskell-navigate-imports.el --- A function for cycling through Haskell import lists -*- lexical-binding: t -*-

;; Copyright (C) 2010  Chris Done

;; Author: Chris Done <chrisdone@gmail.com>

;; 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
;; <http://www.gnu.org/licenses/>.

;;; Commentary:

;; The cycling step will stop once at the last import list so
;; that it is easy to add a new import list.

;; This module works completely independently of any libraries
;; (including haskell-mode).

;; Exports three interactive functions:
;; 1. haskell-navigate-imports
;; 2. haskell-navigate-imports-go
;; 3. haskell-navigate-imports-return

;; Example usage:

;; (require 'haskell-navigate-imports)
;; (define-key haskell-mode-map (kbd "<f8>") 'haskell-navigate-imports)

;;; Code:

(defvar haskell-navigate-imports-start-point nil)

(defvar haskell-literate) ; defined in haskell-mode.el

;;;###autoload
(defun haskell-navigate-imports (&optional return)
  "Cycle the Haskell import lines or return to point (with prefix arg)."
  (interactive "P")
  (if return
      (haskell-navigate-imports-return)
    (haskell-navigate-imports-go)))

;;;###autoload
(defun haskell-navigate-imports-go ()
  "Go to the first line of a list of consecutive import lines. Cycles."
  (interactive)
  (unless (or (haskell-navigate-imports-line)
              (equal (line-beginning-position) (point-min))
              (save-excursion (forward-line -1)
                              (haskell-navigate-imports-line)))
    (setq haskell-navigate-imports-start-point (point)))
  (haskell-navigate-imports-go-internal))

;;;###autoload
(defun haskell-navigate-imports-return ()
  "Return to the non-import point we were at before going to the module list.
   If we were originally at an import list, we can just cycle through easily."
  (interactive)
  (when haskell-navigate-imports-start-point
    (goto-char haskell-navigate-imports-start-point)))

(defun haskell-navigate-imports-go-internal ()
  "Go to the first line of a list of consecutive import lines. Cycle."
  (if (haskell-navigate-imports-line)
      (progn (haskell-navigate-imports-goto-end)
             (when (haskell-navigate-imports-find-forward-line)
               (haskell-navigate-imports-go-internal)))
    (let ((point (haskell-navigate-imports-find-forward-line)))
      (if point
          (goto-char point)
        (progn (goto-char (point-min))
               (if (haskell-navigate-imports-find-forward-line)
                   (haskell-navigate-imports-go-internal)
                 (let ((module (if (eq haskell-literate 'bird)
                                   "^> ?module"
                                 "^module")))
                   (when (search-forward-regexp module nil t 1)
                     (search-forward "\n\n" nil t 1)))))))))

(defun haskell-navigate-imports-goto-end ()
  "Skip a bunch of consecutive import lines."
  (while (not (or (equal (point)
                         (point-max))
                  (not (haskell-navigate-imports-line))))
    (forward-line)))

(defun haskell-navigate-imports-find-forward-line ()
  "Return a point with at an import line, or nothing."
  (save-excursion
    (while (not (or (equal (point) (point-max))
                    (haskell-navigate-imports-after-imports-p) ;; This one just speeds it up.
                    (haskell-navigate-imports-line)))
      (forward-line))
    (if (haskell-navigate-imports-line)
        (point)
        nil)))

(defun haskell-navigate-imports-line ()
  "Try to match the current line as a regexp."
  (let ((line (buffer-substring-no-properties (line-beginning-position)
                                              (line-end-position)))
        (import (if (eq haskell-literate 'bird)
                    "^> ?import "
                  "^import ")))
    (if (string-match import line)
        line
      nil)))

(defun haskell-navigate-imports-after-imports-p ()
  "Are we after the imports list? Just for a speed boost."
  (save-excursion
    (goto-char (line-beginning-position))
    (not (not (search-forward-regexp "\\( = \\|\\<instance\\>\\| :: \\)"
                                     (line-end-position) t 1)))))

(provide 'haskell-navigate-imports)

;;; haskell-navigate-imports.el ends here
