Skip to content

Support simple completions in non-interactive context. Support keyword completions #1172

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Feb 23, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 125 additions & 30 deletions haskell-completions.el
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
;;; haskell-completions.el --- Haskell Completion package -*- lexical-binding: t -*-

;; Copyright © 2015 Athur Fayzrakhmanov. All rights reserved.
;; 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:
Expand Down Expand Up @@ -40,7 +40,7 @@
(require 'haskell-process)
(require 'haskell-interactive-mode)

(defvar haskell-completions-pragma-names
(defvar haskell-completions--pragma-names
(list "DEPRECATED"
"INCLUDE"
"INCOHERENT"
Expand All @@ -63,8 +63,51 @@
"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'.
")
`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"
"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.
Expand Down Expand Up @@ -206,9 +249,20 @@ identifier at point depending on result of function
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
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)
Expand All @@ -220,36 +274,77 @@ result only if prefix length is not less than 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
(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
haskell-completions--keywords))))
(list beg end candidates))))


(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)))
(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.

(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)))))))
(when (not (eql typ 'haskell-completions-general-prefix))
;; 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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I understand there will be two completions hooks installed at the same time, one by haskell-mode other by interactive-haskell-mode. The non-interactive one will return completions for PRAGMA, GHC_OPTION and LANGUAGE, so there is no need to return those completions here again.

Unless I'm mistaken how the mechanism works.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gracjan I tested this, and if first function in hook returns nil there will no completions. I tested this creating two dummy completion functions and added both of them to completion-at-point-functions hook in elisp mode. All time only first function was used to return completion candidates. I don't know why it works this way though.

(defun complete-ab ()
  (let ((end (point)))
    (save-excursion
      (backward-word)
      (when (looking-at "ab")
        (let ((start (point)))
          (list start end '("abcde" "abcdf" "abcdef")))))))

(defun complete-other ()
  (let ((end (point)))
    (save-excursion
      (backward-word)
      (let* ((start (point))
             (word (buffer-substring start end)))
        (list start end '(concat word "123"))))))

(add-hook 'completion-at-point-functions #'complete-other nil t)
(add-hook 'completion-at-point-functions #'complete-ab nil t)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've repeated this experiment and it works like described in Emacs docs (and unlike you say). That means I'm able to complete "1" to "123" and "ab" to any of the options listed. Emacs seems to work correctly.

;; 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))))
;; 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.
Expand Down
3 changes: 2 additions & 1 deletion haskell-interactive-mode.el
Original file line number Diff line number Diff line change
Expand Up @@ -1016,7 +1016,8 @@ don't care when the thing completes as long as it's soonish."
(remove-overlays))))

(defun haskell-interactive-mode-completion-at-point-function ()
"Offer completions for partial expression between prompt and point"
"Offer completions for partial expression between prompt and point.
This completion function is used in interactive REPL buffer itself."
(when (haskell-interactive-at-prompt)
(let* ((process (haskell-interactive-process))
(inp (haskell-interactive-mode-input-partial))
Expand Down
5 changes: 5 additions & 0 deletions haskell-mode.el
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,11 @@ Minor modes that work well with `haskell-mode':
(setq haskell-literate nil)
(add-hook 'before-save-hook 'haskell-mode-before-save-handler nil t)
(add-hook 'after-save-hook 'haskell-mode-after-save-handler nil t)
;; provide non-interactive completion function
(add-hook 'completion-at-point-functions
#'haskell-completions-completion-at-point
nil
t)
(haskell-indentation-mode))

(defun haskell-fill-paragraph (justify)
Expand Down
4 changes: 2 additions & 2 deletions haskell.el
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,12 @@
:lighter " Interactive"
:keymap interactive-haskell-mode-map
(add-hook 'completion-at-point-functions
#'haskell-completions-sync-completions-at-point
#'haskell-completions-sync-repl-completion-at-point
nil
t))

(make-obsolete 'haskell-process-completions-at-point
'haskell-completions-sync-completions-at-point
'haskell-completions-sync-repl-completion-at-point
"June 19, 2015")
(defun haskell-process-completions-at-point ()
"A completion-at-point function using the current haskell process."
Expand Down