|
|
- ;;; haskell-completions.el --- Haskell Completion package -*- lexical-binding: t -*-
-
- ;; Copyright © 2015-2016 Athur Fayzrakhmanov. All rights reserved.
-
- ;; This file is part of haskell-mode package.
- ;; You can contact with authors using GitHub issue tracker:
- ;; https://github.com/haskell/haskell-mode/issues
-
- ;; This file is free software; you can redistribute it and/or modify
- ;; it under the terms of the GNU General Public License as published by
- ;; the Free Software Foundation; either version 3, or (at your option)
- ;; any later version.
-
- ;; This file is distributed in the hope that it will be useful,
- ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
- ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- ;; GNU General Public License for more details.
-
- ;; You should have received a copy of the GNU General Public License
- ;; along with GNU Emacs; see the file COPYING. If not, write to
- ;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- ;; Boston, MA 02110-1301, USA.
-
- ;;; Commentary:
-
- ;; This package provides completions related functionality for
- ;; Haskell Mode such grab completion prefix at point, and etc..
-
- ;; Some description
- ;; ================
- ;;
- ;; For major use function `haskell-completions-grab-prefix' is supposed, and
- ;; other prefix grabbing functions are used internally by it. So, only this
- ;; funciton have prefix minimal length functionality and invokes predicate
- ;; function `haskell-completions-can-grab-prefix'.
-
- ;;; Code:
-
- (require 'haskell-mode)
- (require 'haskell-process)
- (require 'haskell-interactive-mode)
-
- ;;;###autoload
- (defgroup haskell-completions nil
- "Settings for completions provided by `haskell-mode'"
- :link '(custom-manual "(haskell-mode)Completion support")
- :group 'haskell)
-
- (defcustom haskell-completions-complete-operators
- t
- "Should `haskell-completions-sync-repl-completion-at-point' complete operators.
-
- Note: GHCi prior to version 8.0.1 have bug in `:complete`
- command: when completing operators it returns a list of all
- imported identifiers (see Track ticket URL
- `https://ghc.haskell.org/trac/ghc/ticket/10576'). This leads to
- significant Emacs slowdown. To aviod slowdown you should set
- this variable to `nil'."
- :group 'haskell-completions
- :type 'boolean)
-
- (defvar haskell-completions--pragma-names
- (list "DEPRECATED"
- "INCLUDE"
- "INCOHERENT"
- "INLINABLE"
- "INLINE"
- "LANGUAGE"
- "LINE"
- "MINIMAL"
- "NOINLINE"
- "NOUNPACK"
- "OPTIONS"
- "OPTIONS_GHC"
- "OVERLAPPABLE"
- "OVERLAPPING"
- "OVERLAPS"
- "RULES"
- "SOURCE"
- "SPECIALIZE"
- "UNPACK"
- "WARNING")
- "A list of supported pragmas.
- This list comes from GHC documentation (URL
- `https://downloads.haskell.org/~ghc/7.10.1/docs/html/users_guide/pragmas.html'.")
-
- (defvar haskell-completions--keywords
- (list
- "as"
- "case"
- "class"
- "data family"
- "data instance"
- "data"
- "default"
- "deriving instance"
- "deriving"
- "do"
- "else"
- "family"
- "forall"
- "foreign import"
- "foreign"
- "hiding"
- "if"
- "import qualified"
- "import"
- "in"
- "infix"
- "infixl"
- "infixr"
- "instance"
- "let"
- "mdo"
- "module"
- "newtype"
- "of"
- "proc"
- "qualified"
- "rec"
- "signature"
- "then"
- "type family"
- "type instance"
- "type"
- "where")
- "A list of Haskell's keywords (URL `https://wiki.haskell.org/Keywords').
- Single char keywords and operator like keywords are not included
- in this list.")
-
-
- (defun haskell-completions-can-grab-prefix ()
- "Check if the case is appropriate for grabbing completion prefix.
- Returns t if point is either at whitespace character, or at
- punctuation, or at line end and preceeding character is not a
- whitespace or new line, otherwise returns nil.
-
- Returns nil in presence of active region."
- (when (not (region-active-p))
- (when (looking-at-p (rx (| space line-end punct)))
- (when (not (bobp))
- (save-excursion
- (backward-char)
- (not (looking-at-p (rx (| space line-end)))))))))
-
- (defun haskell-completions-grab-pragma-prefix ()
- "Grab completion prefix for pragma completions.
- Returns a list of form '(prefix-start-position
- prefix-end-position prefix-value prefix-type) for pramga names
- such as WARNING, DEPRECATED, LANGUAGE etc. Also returns
- completion prefixes for options in case OPTIONS_GHC pragma, or
- language extensions in case of LANGUAGE pragma. Obsolete OPTIONS
- pragma is supported also."
- (when (nth 4 (syntax-ppss))
- ;; We're inside comment
- (let ((p (point))
- (comment-start (nth 8 (syntax-ppss)))
- (case-fold-search nil)
- prefix-start
- prefix-end
- prefix-type
- prefix-value)
- (save-excursion
- (goto-char comment-start)
- (when (looking-at (rx "{-#" (1+ (| space "\n"))))
- (let ((pragma-start (match-end 0)))
- (when (> p pragma-start)
- ;; point stands after `{-#`
- (goto-char pragma-start)
- (when (looking-at (rx (1+ (| upper "_"))))
- ;; found suitable sequence for pragma name
- (let ((pragma-end (match-end 0))
- (pragma-value (match-string-no-properties 0)))
- (if (eq p pragma-end)
- ;; point is at the end of (in)complete pragma name
- ;; prepare resulting values
- (progn
- (setq prefix-start pragma-start)
- (setq prefix-end pragma-end)
- (setq prefix-value pragma-value)
- (setq prefix-type
- 'haskell-completions-pragma-name-prefix))
- (when (and (> p pragma-end)
- (or (equal "OPTIONS_GHC" pragma-value)
- (equal "OPTIONS" pragma-value)
- (equal "LANGUAGE" pragma-value)))
- ;; point is after pragma name, so we need to check
- ;; special cases of `OPTIONS_GHC` and `LANGUAGE` pragmas
- ;; and provide a completion prefix for possible ghc
- ;; option or language extension.
- (goto-char pragma-end)
- (when (re-search-forward
- (rx (* anything)
- (1+ (regexp "\\S-")))
- p
- t)
- (let* ((str (match-string-no-properties 0))
- (split (split-string str (rx (| space "\n")) t))
- (val (car (last split)))
- (end (point)))
- (when (and (equal p end)
- (not (string-match-p "#" val)))
- (setq prefix-value val)
- (backward-char (length val))
- (setq prefix-start (point))
- (setq prefix-end end)
- (setq
- prefix-type
- (if (not (equal "LANGUAGE" pragma-value))
- 'haskell-completions-ghc-option-prefix
- 'haskell-completions-language-extension-prefix
- )))))))))))))
- (when prefix-value
- (list prefix-start prefix-end prefix-value prefix-type)))))
-
- (defun haskell-completions-grab-identifier-prefix ()
- "Grab completion prefix for identifier at point.
- Returns a list of form '(prefix-start-position
- prefix-end-position prefix-value prefix-type) for haskell
- identifier at point depending on result of function
- `haskell-ident-pos-at-point'."
- (let ((pos-at-point (haskell-ident-pos-at-point))
- (p (point)))
- (when pos-at-point
- (let* ((start (car pos-at-point))
- (end (cdr pos-at-point))
- (type 'haskell-completions-identifier-prefix)
- (case-fold-search nil)
- value)
- ;; we need end position of result, becase of
- ;; `haskell-ident-pos-at-point' ignores trailing whitespace, e.g. the
- ;; result will be same for `map|` and `map |` invocations.
- (when (<= p end)
- (setq end p)
- (setq value (buffer-substring-no-properties start end))
- (when (string-match-p (rx bos upper) value)
- ;; we need to check if found identifier is a module name
- (save-excursion
- (goto-char (line-beginning-position))
- (when (re-search-forward
- (rx "import"
- (? (1+ space) "qualified")
- (1+ space)
- upper
- (1+ (| alnum ".")))
- p ;; bound
- t) ;; no-error
- (if (equal p (point))
- (setq type 'haskell-completions-module-name-prefix)
- (when (re-search-forward
- (rx (| " as " "("))
- start
- t)
- ;; but uppercase ident could occur after `as` keyword, or in
- ;; module imports after opening parenthesis, in this case
- ;; restore identifier type again, it's neccessary to
- ;; distinguish the means of completions retrieval
- (setq type 'haskell-completions-identifier-prefix))))))
- (when (nth 8 (syntax-ppss))
- ;; eighth element of syntax-ppss result is string or comment start,
- ;; so when it's not nil word at point is inside string or comment,
- ;; return special literal prefix type
- (setq type 'haskell-completions-general-prefix))
- ;; finally take in account minlen if given and return the result
- (when value (list start end value type)))))))
-
- (defun haskell-completions-grab-prefix (&optional minlen)
- "Grab prefix at point for possible completion.
- Returns a list of form '(prefix-start-position
- prefix-end-position prefix-value prefix-type) depending on
- situation, e.g. is it needed to complete pragma, module name,
- arbitrary identifier, etc. Returns nil in case it is
- impossible to grab prefix.
-
- Possible prefix types are:
-
- * haskell-completions-pragma-name-prefix
- * haskell-completions-ghc-option-prefix
- * haskell-completions-language-extension-prefix
- * haskell-completions-module-name-prefix
- * haskell-completions-identifier-prefix
- * haskell-completions-general-prefix
-
- the last type is used in cases when completing things inside comments.
-
- If provided optional MINLEN parameter this function will return
- result only if prefix length is not less than MINLEN."
- (when (haskell-completions-can-grab-prefix)
- (let ((prefix (cond
- ((haskell-completions-grab-pragma-prefix))
- ((haskell-completions-grab-identifier-prefix)))))
- (cond ((and minlen prefix)
- (when (>= (length (nth 2 prefix)) minlen)
- prefix))
- (prefix prefix)))))
-
- (defun haskell-completions--simple-completions (prefix)
- "Provide a list of completion candidates for given PREFIX.
- This function is used internally in
- `haskell-completions-completion-at-point' and
- `haskell-completions-sync-repl-completion-at-point'.
-
- It provides completions for haskell keywords, language pragmas,
- GHC's options, and language extensions.
-
- PREFIX should be a list such one returned by
- `haskell-completions-grab-identifier-prefix'."
- (cl-destructuring-bind (beg end _pfx typ) prefix
- (when (not (eql typ 'haskell-completions-general-prefix))
- (let ((candidates
- (cl-case typ
- ('haskell-completions-pragma-name-prefix
- haskell-completions--pragma-names)
- ('haskell-completions-ghc-option-prefix
- haskell-ghc-supported-options)
- ('haskell-completions-language-extension-prefix
- haskell-ghc-supported-extensions)
- (otherwise
- (append (when (bound-and-true-p haskell-tags-on-save)
- tags-completion-table)
- haskell-completions--keywords)))))
- (list beg end candidates)))))
-
- ;;;###autoload
- (defun haskell-completions-completion-at-point ()
- "Provide completion list for thing at point.
- This function is used in non-interactive `haskell-mode'. It
- provides completions for haskell keywords, language pragmas,
- GHC's options, and language extensions, but not identifiers."
- (let ((prefix (haskell-completions-grab-prefix)))
- (when prefix
- (haskell-completions--simple-completions prefix))))
-
- (defun haskell-completions-sync-repl-completion-at-point ()
- "A completion function used in `interactive-haskell-mode'.
- Completion candidates are provided quering current haskell
- process, that is sending `:complete repl' command.
-
- Completes all possible things: everything that can be completed
- with non-interactive function
- `haskell-completions-completion-at-point' plus identifier
- completions.
-
- Returns nil if no completions available."
- (let ((prefix-data (haskell-completions-grab-prefix)))
- (when prefix-data
- (cl-destructuring-bind (beg end pfx typ) prefix-data
- (when (and (not (eql typ 'haskell-completions-general-prefix))
- (or haskell-completions-complete-operators
- (not (save-excursion
- (goto-char (1- end))
- (haskell-mode--looking-at-varsym)))))
- ;; do not complete things in comments
- (if (cl-member
- typ
- '(haskell-completions-pragma-name-prefix
- haskell-completions-ghc-option-prefix
- haskell-completions-language-extension-prefix))
- ;; provide simple completions
- (haskell-completions--simple-completions prefix-data)
- ;; only two cases left: haskell-completions-module-name-prefix
- ;; and haskell-completions-identifier-prefix
- (let* ((is-import (eql typ 'haskell-completions-module-name-prefix))
- (candidates
- (when (and (haskell-session-maybe)
- (not (haskell-process-cmd
- (haskell-interactive-process)))
- ;; few possible extra checks would be:
- ;; (haskell-process-get 'is-restarting)
- ;; (haskell-process-get 'evaluating)
- )
- ;; if REPL is available and not busy try to query it for
- ;; completions list in case of module name or identifier
- ;; prefixes
- (haskell-completions-sync-complete-repl pfx is-import))))
- ;; append candidates with keywords
- (list beg end (append
- candidates
- haskell-completions--keywords)))))))))
-
- (defun haskell-completions-sync-complete-repl (prefix &optional import)
- "Return completion list for given PREFIX querying REPL synchronously.
- When optional IMPORT argument is non-nil complete PREFIX
- prepending \"import \" keyword (useful for module names). This
- function is supposed for internal use."
- (haskell-process-get-repl-completions
- (haskell-interactive-process)
- (if import
- (concat "import " prefix)
- prefix)))
-
- (provide 'haskell-completions)
- ;;; haskell-completions.el ends here
|