You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

267 lines
11 KiB

;;; haskell-completions.el --- Haskell Completion package -*- lexical-binding: t -*-
;; Copyright © 2015 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)
(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'.
")
(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 presense 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 and 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, and etc. Rerurns nil in case it is
impossible to grab prefix.
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-sync-completions-at-point ()
"A `completion-at-point' function using the current haskell process.
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
(let ((imp (eql typ 'haskell-completions-module-name-prefix))
lst)
(setq lst
(cl-case typ
;; non-interactive completions first
('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
(when (and
(not (eql typ 'haskell-completions-general-prefix))
(haskell-session-maybe)
(not
(haskell-process-cmd (haskell-interactive-process))))
;; 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 imp)))))
(when lst
(list beg end lst)))))))
(defun haskell-completions-sync-complete-repl (prefix &optional import)
"Return completion list for given PREFIX quering 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