|
1 | 1 | ;;; haskell-commands.el --- Commands that can be run on the process
|
2 | 2 |
|
| 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 | + |
3 | 9 | ;; Copyright (c) 2014 Chris Done. All rights reserved.
|
4 | 10 |
|
5 | 11 | ;; This file is free software; you can redistribute it and/or modify
|
@@ -495,36 +501,6 @@ GHCi."
|
495 | 501 | (error (propertize "No reply. Is :loc-at supported?"
|
496 | 502 | 'face 'compilation-error)))))))
|
497 | 503 |
|
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 |
| - |
528 | 504 | ;;;###autoload
|
529 | 505 | (defun haskell-process-cd (&optional not-interactive)
|
530 | 506 | "Change directory."
|
@@ -614,35 +590,77 @@ command from GHCi."
|
614 | 590 | (string-match "^<interactive>" response))
|
615 | 591 | (haskell-mode-message-line response)))))))
|
616 | 592 |
|
| 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 | + |
617 | 597 | ;;;###autoload
|
618 | 598 | (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)." |
620 | 604 | (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))))))) |
646 | 664 |
|
647 | 665 | ;;;###autoload
|
648 | 666 | (defun haskell-process-generate-tags (&optional and-then-find-this-tag)
|
@@ -856,4 +874,101 @@ the :uses command from GHCi."
|
856 | 874 | (error (propertize "No reply. Is :uses supported?"
|
857 | 875 | 'face 'compilation-error)))))))
|
858 | 876 |
|
| 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 | + |
859 | 973 | (provide 'haskell-commands)
|
| 974 | +;;; haskell-commands.el ends here |
0 commit comments