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.
266 lines
11 KiB
266 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
|
|
|