Skip to content

Commit 2532074

Browse files
committed
Merge pull request #598 from geraldus/present-type
Support presentaion mode by `haskell-mode-show-type-at`
2 parents 2d90ce5 + 0034b0a commit 2532074

File tree

1 file changed

+171
-56
lines changed

1 file changed

+171
-56
lines changed

haskell-commands.el

Lines changed: 171 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
;;; haskell-commands.el --- Commands that can be run on the process
22

3+
;;; Commentary:
4+
5+
;;; This module provides varoius `haskell-mode' and `haskell-interactive-mode'
6+
;;; specific commands such as show type signature, show info, haskell process
7+
;;; commands and etc.
8+
39
;; Copyright (c) 2014 Chris Done. All rights reserved.
410

511
;; This file is free software; you can redistribute it and/or modify
@@ -495,36 +501,6 @@ GHCi."
495501
(error (propertize "No reply. Is :loc-at supported?"
496502
'face 'compilation-error)))))))
497503

498-
(defun haskell-mode-type-at ()
499-
"Get the type of the thing at point. Requires the :type-at
500-
command from GHCi."
501-
(let ((pos (or (when (region-active-p)
502-
(cons (region-beginning)
503-
(region-end)))
504-
(haskell-spanable-pos-at-point)
505-
(cons (point)
506-
(point)))))
507-
(when pos
508-
(replace-regexp-in-string
509-
"\n$"
510-
""
511-
(save-excursion
512-
(haskell-process-queue-sync-request
513-
(haskell-interactive-process)
514-
(replace-regexp-in-string
515-
"\n"
516-
" "
517-
(format ":type-at %s %d %d %d %d %s"
518-
(buffer-file-name)
519-
(progn (goto-char (car pos))
520-
(line-number-at-pos))
521-
(1+ (current-column))
522-
(progn (goto-char (cdr pos))
523-
(line-number-at-pos))
524-
(1+ (current-column))
525-
(buffer-substring-no-properties (car pos)
526-
(cdr pos))))))))))
527-
528504
;;;###autoload
529505
(defun haskell-process-cd (&optional not-interactive)
530506
"Change directory."
@@ -614,35 +590,77 @@ command from GHCi."
614590
(string-match "^<interactive>" response))
615591
(haskell-mode-message-line response)))))))
616592

593+
(defvar hs-utils/async-post-command-flag nil
594+
"Non-nil means some commands were triggered during async function execution.")
595+
(make-variable-buffer-local 'hs-utils/async-post-command-flag)
596+
617597
;;;###autoload
618598
(defun haskell-mode-show-type-at (&optional insert-value)
619-
"Show the type of the thing at point."
599+
"Show type of the thing at point or within active region asynchronously.
600+
Optional argument INSERT-VALUE indicates that recieved type signature should be
601+
inserted (but only if nothing happened since function invocation).
602+
This function requires GHCi-ng (see
603+
https://github.com/chrisdone/ghci-ng#using-with-haskell-mode for instructions)."
620604
(interactive "P")
621-
(let ((ty (haskell-mode-type-at))
622-
(orig (point)))
623-
(unless (= (aref ty 0) ?\n)
624-
;; That seems to be what happens when `haskell-mode-type-at` fails
625-
(if insert-value
626-
(let ((ident-pos (or (haskell-ident-pos-at-point)
627-
(cons (point) (point)))))
628-
(cond
629-
((region-active-p)
630-
(delete-region (region-beginning)
631-
(region-end))
632-
(insert "(" ty ")")
633-
(goto-char (1+ orig)))
634-
((= (line-beginning-position) (car ident-pos))
635-
(goto-char (line-beginning-position))
636-
(insert (haskell-fontify-as-mode ty 'haskell-mode)
637-
"\n"))
638-
(t
639-
(save-excursion
640-
(goto-char (car ident-pos))
641-
(let ((col (current-column)))
642-
(save-excursion (insert "\n")
643-
(indent-to col))
644-
(insert (haskell-fontify-as-mode ty 'haskell-mode)))))))
645-
(message "%s" (haskell-fontify-as-mode ty 'haskell-mode))))))
605+
(let* ((pos (hs-utils/capture-expr-bounds))
606+
(req (hs-utils/compose-type-at-command pos))
607+
(process (haskell-interactive-process))
608+
(buf (current-buffer))
609+
(pos-reg (cons pos (region-active-p))))
610+
(haskell-process-queue-command
611+
process
612+
(make-haskell-command
613+
:state (list process req buf insert-value pos-reg)
614+
:go
615+
(lambda (state)
616+
(let* ((prc (car state))
617+
(req (nth 1 state)))
618+
(hs-utils/async-watch-changes)
619+
(haskell-process-send-string prc req)))
620+
:complete
621+
(lambda (state response)
622+
(let* ((init-buffer (nth 2 state))
623+
(insert-value (nth 3 state))
624+
(pos-reg (nth 4 state))
625+
(wrap (cdr pos-reg))
626+
(min-pos (caar pos-reg))
627+
(max-pos (cdar pos-reg))
628+
(sig (hs-utils/reduce-string response))
629+
(split (split-string sig "\\W::\\W" t))
630+
(is-error (not (= (length split) 2))))
631+
632+
(if is-error
633+
;; neither popup presentation buffer
634+
;; nor insert response in error case
635+
(message "Wrong REPL response: %s" sig)
636+
(if insert-value
637+
;; Only insert type signature and do not present it
638+
(if (= (length hs-utils/async-post-command-flag) 1)
639+
(if wrap
640+
;; Handle region case
641+
(progn
642+
(deactivate-mark)
643+
(save-excursion
644+
(delete-region min-pos max-pos)
645+
(goto-char min-pos)
646+
(insert (concat "(" sig ")"))))
647+
;; Non-region cases
648+
(hs-utils/insert-type-signature sig))
649+
;; Some commands registered, prevent insertion
650+
(let* ((rev (reverse hs-utils/async-post-command-flag))
651+
(cs (format "%s" (cdr rev))))
652+
(message
653+
(concat
654+
"Type signature insertion was prevented. "
655+
"These commands were registered:"
656+
cs))))
657+
;; Present the result only when response is valid and not asked to
658+
;; insert result
659+
(let* ((expr (car split))
660+
(buf-name (concat ":type " expr)))
661+
(hs-utils/echo-or-present response buf-name))))
662+
663+
(hs-utils/async-stop-watching-changes init-buffer)))))))
646664

647665
;;;###autoload
648666
(defun haskell-process-generate-tags (&optional and-then-find-this-tag)
@@ -856,4 +874,101 @@ the :uses command from GHCi."
856874
(error (propertize "No reply. Is :uses supported?"
857875
'face 'compilation-error)))))))
858876

877+
(defun hs-utils/capture-expr-bounds ()
878+
"Capture position bounds of expression at point.
879+
If there is an active region then it returns region
880+
bounds. Otherwise it uses `haskell-spanable-pos-at-point` to
881+
capture identifier bounds. If latter function returns NIL this function
882+
will return cons cell where min and max positions both are equal
883+
to point."
884+
(or (when (region-active-p)
885+
(cons (region-beginning)
886+
(region-end)))
887+
(haskell-spanable-pos-at-point)
888+
(cons (point) (point))))
889+
890+
(defun hs-utils/compose-type-at-command (pos)
891+
"Prepare :type-at command to be send to haskell process.
892+
POS is a cons cell containing min and max positions, i.e. target
893+
expression bounds."
894+
(replace-regexp-in-string
895+
"\n$"
896+
""
897+
(format ":type-at %s %d %d %d %d %s"
898+
(buffer-file-name)
899+
(progn (goto-char (car pos))
900+
(line-number-at-pos))
901+
(1+ (current-column))
902+
(progn (goto-char (cdr pos))
903+
(line-number-at-pos))
904+
(1+ (current-column))
905+
(buffer-substring-no-properties (car pos)
906+
(cdr pos)))))
907+
908+
(defun hs-utils/reduce-string (s)
909+
"Remove newlines ans extra whitespace from S.
910+
Removes all extra whitespace at the beginning of each line leaving
911+
only single one. Then removes all newlines."
912+
(let ((s_ (replace-regexp-in-string "^\s+" " " s)))
913+
(replace-regexp-in-string "\n" "" s_)))
914+
915+
(defun hs-utils/insert-type-signature (signature)
916+
"Insert type signature.
917+
In case of active region is present, wrap it by parentheses and
918+
append SIGNATURE to original expression. Otherwise tries to
919+
carefully insert SIGNATURE above identifier at point. Removes
920+
newlines and extra whitespace in signature before insertion."
921+
(let* ((ident-pos (or (haskell-ident-pos-at-point)
922+
(cons (point) (point))))
923+
(min-pos (car ident-pos))
924+
(sig (hs-utils/reduce-string signature)))
925+
(save-excursion
926+
(goto-char min-pos)
927+
(let ((col (current-column)))
928+
(insert sig "\n")
929+
(indent-to col)))))
930+
931+
(defun hs-utils/echo-or-present (msg &optional name)
932+
"Present message in some manner depending on configuration.
933+
If variable `haskell-process-use-presentation-mode' is NIL it will output
934+
modified message MSG to echo area.
935+
Optinal NAME will be used as presentation mode buffer name."
936+
(if haskell-process-use-presentation-mode
937+
(let ((bufname (or name "*Haskell Presentation*"))
938+
(session (haskell-process-session (haskell-interactive-process))))
939+
(haskell-present bufname session msg))
940+
(let (m (hs-utils/reduce-string msg))
941+
(message m))))
942+
943+
(defun hs-utils/async-update-post-command-flag ()
944+
"A special hook which collects triggered commands during async execution.
945+
This hook pushes value of variable `this-command' to flag variable
946+
`hs-utils/async-post-command-flag'."
947+
(let* ((cmd this-command)
948+
(updated-flag (cons cmd hs-utils/async-post-command-flag)))
949+
(setq hs-utils/async-post-command-flag updated-flag)))
950+
951+
(defun hs-utils/async-watch-changes ()
952+
"Watch for triggered commands during async operation execution.
953+
Resets flag variable
954+
`hs-utils/async-update-post-command-flag' to NIL. By chanhges it is
955+
assumed that nothing happened, e.g. nothing was inserted in
956+
buffer, point was not moved, etc. To collect data `post-command-hook' is used."
957+
(setq hs-utils/async-post-command-flag nil)
958+
(add-hook
959+
'post-command-hook #'hs-utils/async-update-post-command-flag nil t))
960+
961+
(defun hs-utils/async-stop-watching-changes (buffer)
962+
"Clean up after async operation finished.
963+
This function takes care about cleaning up things made by
964+
`hs-utils/async-watch-changes'. The BUFFER argument is a buffer where
965+
`post-command-hook' should be disabled. This is neccessary, because
966+
it is possible that user will change buffer during async function
967+
execusion."
968+
(with-current-buffer buffer
969+
(setq hs-utils/async-post-command-flag nil)
970+
(remove-hook
971+
'post-command-hook #'hs-utils/async-update-post-command-flag t)))
972+
859973
(provide 'haskell-commands)
974+
;;; haskell-commands.el ends here

0 commit comments

Comments
 (0)