Klimi's new dotfiles with stow.
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

966 rader
41 KiB

5 år sedan
  1. ;;; haskell-commands.el --- Commands that can be run on the process -*- lexical-binding: t -*-
  2. ;;; Commentary:
  3. ;;; This module provides varoius `haskell-mode' and `haskell-interactive-mode'
  4. ;;; specific commands such as show type signature, show info, haskell process
  5. ;;; commands and etc.
  6. ;; Copyright © 2014 Chris Done. All rights reserved.
  7. ;; 2016 Arthur Fayzrakhmanov
  8. ;; This file is free software; you can redistribute it and/or modify
  9. ;; it under the terms of the GNU General Public License as published by
  10. ;; the Free Software Foundation; either version 3, or (at your option)
  11. ;; any later version.
  12. ;; This file is distributed in the hope that it will be useful,
  13. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. ;; GNU General Public License for more details.
  16. ;; You should have received a copy of the GNU General Public License
  17. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. ;;; Code:
  19. (require 'cl-lib)
  20. (require 'etags)
  21. (require 'haskell-mode)
  22. (require 'haskell-compat)
  23. (require 'haskell-process)
  24. (require 'haskell-font-lock)
  25. (require 'haskell-interactive-mode)
  26. (require 'haskell-session)
  27. (require 'haskell-string)
  28. (require 'haskell-presentation-mode)
  29. (require 'haskell-utils)
  30. (require 'highlight-uses-mode)
  31. (require 'haskell-cabal)
  32. (defcustom haskell-mode-stylish-haskell-path "stylish-haskell"
  33. "Path to `stylish-haskell' executable."
  34. :group 'haskell
  35. :type 'string)
  36. (defcustom haskell-mode-stylish-haskell-args nil
  37. "Arguments to pass to program specified by haskell-mode-stylish-haskell-path."
  38. :group 'haskell
  39. :type 'list)
  40. (defcustom haskell-interactive-set-+c
  41. t
  42. "Issue ':set +c' in interactive session to support type introspection."
  43. :group 'haskell-interactive
  44. :type 'boolean)
  45. ;;;###autoload
  46. (defun haskell-process-restart ()
  47. "Restart the inferior Haskell process."
  48. (interactive)
  49. (haskell-process-reset (haskell-interactive-process))
  50. (haskell-process-set (haskell-interactive-process) 'command-queue nil)
  51. (haskell-process-start (haskell-interactive-session)))
  52. (defun haskell-process-start (session)
  53. "Start the inferior Haskell process with a given SESSION.
  54. You can create new session using function `haskell-session-make'."
  55. (let ((existing-process (get-process (haskell-session-name (haskell-interactive-session)))))
  56. (when (processp existing-process)
  57. (haskell-interactive-mode-echo session "Restarting process ...")
  58. (haskell-process-set (haskell-session-process session) 'is-restarting t)
  59. (delete-process existing-process)))
  60. (let ((process (or (haskell-session-process session)
  61. (haskell-process-make (haskell-session-name session))))
  62. (old-queue (haskell-process-get (haskell-session-process session)
  63. 'command-queue)))
  64. (haskell-session-set-process session process)
  65. (haskell-process-set-session process session)
  66. (haskell-process-set-cmd process nil)
  67. (haskell-process-set (haskell-session-process session) 'is-restarting nil)
  68. (let ((default-directory (haskell-session-cabal-dir session))
  69. (log-and-command (haskell-process-compute-process-log-and-command session (haskell-process-type))))
  70. (haskell-session-prompt-set-current-dir session (not haskell-process-load-or-reload-prompt))
  71. (haskell-process-set-process
  72. process
  73. (progn
  74. (haskell-process-log (propertize (format "%S" log-and-command)))
  75. (apply #'start-process (cdr log-and-command)))))
  76. (progn (set-process-sentinel (haskell-process-process process) 'haskell-process-sentinel)
  77. (set-process-filter (haskell-process-process process) 'haskell-process-filter))
  78. (haskell-process-send-startup process)
  79. (unless (or (eq 'cabal-repl (haskell-process-type))
  80. (eq 'cabal-new-repl (haskell-process-type))
  81. (eq 'stack-ghci (haskell-process-type))) ;; Both "cabal repl" and "stack ghci" set the proper CWD.
  82. (haskell-process-change-dir session
  83. process
  84. (haskell-session-current-dir session)))
  85. (haskell-process-set process 'command-queue
  86. (append (haskell-process-get (haskell-session-process session)
  87. 'command-queue)
  88. old-queue))
  89. process))
  90. (defun haskell-process-send-startup (process)
  91. "Send the necessary start messages to haskell PROCESS."
  92. (haskell-process-queue-command
  93. process
  94. (make-haskell-command
  95. :state process
  96. :go (lambda (process)
  97. ;; We must set the prompt last, so that this command as a
  98. ;; whole produces only one prompt marker as a response.
  99. (haskell-process-send-string process
  100. (mapconcat #'identity
  101. (append '("Prelude.putStrLn \"\""
  102. ":set -v1")
  103. (when haskell-interactive-set-+c
  104. '(":set +c"))) ; :type-at in GHC 8+
  105. "\n"))
  106. (haskell-process-send-string process ":set prompt \"\\4\"")
  107. (haskell-process-send-string process (format ":set prompt2 \"%s\""
  108. haskell-interactive-prompt2)))
  109. :live (lambda (process buffer)
  110. (when (haskell-process-consume
  111. process
  112. "^\*\*\* WARNING: \\(.+\\) is writable by someone else, IGNORING!$")
  113. (let ((path (match-string 1 buffer)))
  114. (haskell-session-modify
  115. (haskell-process-session process)
  116. 'ignored-files
  117. (lambda (files)
  118. (cl-remove-duplicates (cons path files) :test 'string=)))
  119. (haskell-interactive-mode-compile-warning
  120. (haskell-process-session process)
  121. (format "GHCi is ignoring: %s (run M-x haskell-process-unignore)"
  122. path)))))
  123. :complete (lambda (process _)
  124. (haskell-interactive-mode-echo
  125. (haskell-process-session process)
  126. (concat (nth (random (length haskell-process-greetings))
  127. haskell-process-greetings)
  128. (when haskell-process-show-debug-tips
  129. "
  130. If I break, you can:
  131. 1. Restart: M-x haskell-process-restart
  132. 2. Configure logging: C-h v haskell-process-log (useful for debugging)
  133. 3. General config: M-x customize-mode
  134. 4. Hide these tips: C-h v haskell-process-show-debug-tips")))
  135. (with-current-buffer (haskell-interactive-buffer)
  136. (goto-char haskell-interactive-mode-prompt-start))))))
  137. (defun haskell-commands-process ()
  138. "Get the Haskell session, throws an error if not available."
  139. (or (haskell-session-process (haskell-session-maybe))
  140. (error "No Haskell session/process associated with this
  141. buffer. Maybe run M-x haskell-session-change?")))
  142. ;;;###autoload
  143. (defun haskell-process-clear ()
  144. "Clear the current process."
  145. (interactive)
  146. (haskell-process-reset (haskell-commands-process))
  147. (haskell-process-set (haskell-commands-process) 'command-queue nil))
  148. ;;;###autoload
  149. (defun haskell-process-interrupt ()
  150. "Interrupt the process (SIGINT)."
  151. (interactive)
  152. (interrupt-process (haskell-process-process (haskell-commands-process))))
  153. (defun haskell-process-reload-with-fbytecode (process module-buffer)
  154. "Query a PROCESS to reload MODULE-BUFFER with -fbyte-code set.
  155. Restores -fobject-code after reload finished.
  156. MODULE-BUFFER is the actual Emacs buffer of the module being loaded."
  157. (haskell-process-queue-without-filters process ":set -fbyte-code")
  158. ;; We prefix the module's filename with a "*", which asks ghci to
  159. ;; ignore any existing object file and interpret the module.
  160. ;; Dependencies will still use their object files as usual.
  161. (haskell-process-queue-without-filters
  162. process
  163. (format ":load \"*%s\""
  164. (replace-regexp-in-string
  165. "\""
  166. "\\\\\""
  167. (buffer-file-name module-buffer))))
  168. (haskell-process-queue-without-filters process ":set -fobject-code"))
  169. (defvar url-http-response-status)
  170. (defvar url-http-end-of-headers)
  171. (defvar haskell-cabal-targets-history nil
  172. "History list for session targets.")
  173. (defun haskell-process-hayoo-ident (ident)
  174. "Hayoo for IDENT, return a list of modules"
  175. ;; We need a real/simulated closure, because otherwise these
  176. ;; variables will be unbound when the url-retrieve callback is
  177. ;; called.
  178. ;; TODO: Remove when this code is converted to lexical bindings by
  179. ;; default (Emacs 24.1+)
  180. (let ((url (format haskell-process-hayoo-query-url (url-hexify-string ident))))
  181. (with-current-buffer (url-retrieve-synchronously url)
  182. (if (= 200 url-http-response-status)
  183. (progn
  184. (goto-char url-http-end-of-headers)
  185. (let* ((res (json-read))
  186. (results (assoc-default 'result res)))
  187. ;; TODO: gather packages as well, and when we choose a
  188. ;; given import, check that we have the package in the
  189. ;; cabal file as well.
  190. (cl-mapcan (lambda (r)
  191. ;; append converts from vector -> list
  192. (append (assoc-default 'resultModules r) nil))
  193. results)))
  194. (warn "HTTP error %s fetching %s" url-http-response-status url)))))
  195. (defun haskell-process-hoogle-ident (ident)
  196. "Hoogle for IDENT, return a list of modules."
  197. (with-temp-buffer
  198. (let ((hoogle-error (call-process "hoogle" nil t nil "search" "--exact" ident)))
  199. (goto-char (point-min))
  200. (unless (or (/= 0 hoogle-error)
  201. (looking-at "^No results found")
  202. (looking-at "^package "))
  203. (while (re-search-forward "^\\([^ ]+\\).*$" nil t)
  204. (replace-match "\\1" nil nil))
  205. (cl-remove-if (lambda (a) (string= "" a))
  206. (split-string (buffer-string)
  207. "\n"))))))
  208. (defun haskell-process-haskell-docs-ident (ident)
  209. "Search with haskell-docs for IDENT, return a list of modules."
  210. (cl-remove-if-not
  211. (lambda (a) (string-match "^[[:upper:]][[:alnum:]_'.]+$" a))
  212. (split-string
  213. (with-output-to-string
  214. (with-current-buffer
  215. standard-output
  216. (call-process "haskell-docs"
  217. nil ; no infile
  218. t ; output to current buffer (that is string)
  219. nil ; do not redisplay
  220. "--modules" ident)))
  221. "\n")))
  222. (defun haskell-process-import-modules (process modules)
  223. "Query PROCESS `:m +' command to import MODULES."
  224. (when haskell-process-auto-import-loaded-modules
  225. (haskell-process-queue-command
  226. process
  227. (make-haskell-command
  228. :state (cons process modules)
  229. :go (lambda (state)
  230. (haskell-process-send-string
  231. (car state)
  232. (format ":m + %s" (mapconcat 'identity (cdr state) " "))))))))
  233. ;;;###autoload
  234. (defun haskell-describe (ident)
  235. "Describe the given identifier IDENT."
  236. (interactive (list (read-from-minibuffer "Describe identifier: "
  237. (haskell-ident-at-point))))
  238. (let ((results (read (shell-command-to-string
  239. (concat "haskell-docs --sexp "
  240. ident)))))
  241. (help-setup-xref (list #'haskell-describe ident)
  242. (called-interactively-p 'interactive))
  243. (save-excursion
  244. (with-help-window (help-buffer)
  245. (with-current-buffer (help-buffer)
  246. (if results
  247. (cl-loop for result in results
  248. do (insert (propertize ident 'font-lock-face
  249. '((:inherit font-lock-type-face
  250. :underline t)))
  251. " is defined in "
  252. (let ((module (cadr (assoc 'module result))))
  253. (if module
  254. (concat module " ")
  255. ""))
  256. (cadr (assoc 'package result))
  257. "\n\n")
  258. do (let ((type (cadr (assoc 'type result))))
  259. (when type
  260. (insert (haskell-fontify-as-mode type 'haskell-mode)
  261. "\n")))
  262. do (let ((args (cadr (assoc 'type results))))
  263. (cl-loop for arg in args
  264. do (insert arg "\n"))
  265. (insert "\n"))
  266. do (insert (cadr (assoc 'documentation result)))
  267. do (insert "\n\n"))
  268. (insert "No results for " ident)))))))
  269. ;;;###autoload
  270. (defun haskell-rgrep (&optional prompt)
  271. "Grep the effective project for the symbol at point.
  272. Very useful for codebase navigation.
  273. Prompts for an arbitrary regexp given a prefix arg PROMPT."
  274. (interactive "P")
  275. (let ((sym (if prompt
  276. (read-from-minibuffer "Look for: ")
  277. (haskell-ident-at-point))))
  278. (rgrep sym
  279. "*.hs *.lhs *.hsc *.chs *.hs-boot *.lhs-boot"
  280. (haskell-session-current-dir (haskell-interactive-session)))))
  281. ;;;###autoload
  282. (defun haskell-process-do-info (&optional prompt-value)
  283. "Print info on the identifier at point.
  284. If PROMPT-VALUE is non-nil, request identifier via mini-buffer."
  285. (interactive "P")
  286. (let ((at-point (haskell-ident-at-point)))
  287. (when (or prompt-value at-point)
  288. (let* ((ident (replace-regexp-in-string
  289. "^!\\([A-Z_a-z]\\)"
  290. "\\1"
  291. (if prompt-value
  292. (read-from-minibuffer "Info: " at-point)
  293. at-point)))
  294. (modname (unless prompt-value
  295. (haskell-utils-parse-import-statement-at-point)))
  296. (command (cond
  297. (modname
  298. (format ":browse! %s" modname))
  299. ((string= ident "") ; For the minibuffer input case
  300. nil)
  301. (t (format (if (string-match "^[a-zA-Z_]" ident)
  302. ":info %s"
  303. ":info (%s)")
  304. (or ident
  305. at-point))))))
  306. (when command
  307. (haskell-process-show-repl-response command))))))
  308. ;;;###autoload
  309. (defun haskell-process-do-type (&optional insert-value)
  310. "Print the type of the given expression.
  311. Given INSERT-VALUE prefix indicates that result type signature
  312. should be inserted."
  313. (interactive "P")
  314. (if insert-value
  315. (haskell-process-insert-type)
  316. (let* ((expr
  317. (if (use-region-p)
  318. (buffer-substring-no-properties (region-beginning) (region-end))
  319. (haskell-ident-at-point)))
  320. (expr-okay (and expr
  321. (not (string-match-p "\\`[[:space:]]*\\'" expr))
  322. (not (string-match-p "\n" expr)))))
  323. ;; No newlines in expressions, and surround with parens if it
  324. ;; might be a slice expression
  325. (when expr-okay
  326. (haskell-process-show-repl-response
  327. (format
  328. (if (or (string-match-p "\\`(" expr)
  329. (string-match-p "\\`[_[:alpha:]]" expr))
  330. ":type %s"
  331. ":type (%s)")
  332. expr))))))
  333. ;;;###autoload
  334. (defun haskell-mode-jump-to-def-or-tag (&optional _next-p)
  335. ;; FIXME NEXT-P arg is not used
  336. "Jump to the definition.
  337. Jump to definition of identifier at point by consulting GHCi, or
  338. tag table as fallback.
  339. Remember: If GHCi is busy doing something, this will delay, but
  340. it will always be accurate, in contrast to tags, which always
  341. work but are not always accurate.
  342. If the definition or tag is found, the location from which you jumped
  343. will be pushed onto `xref--marker-ring', so you can return to that
  344. position with `xref-pop-marker-stack'."
  345. (interactive "P")
  346. (if (haskell-session-maybe)
  347. (let ((initial-loc (point-marker))
  348. (loc (haskell-mode-find-def (haskell-ident-at-point))))
  349. (haskell-mode-handle-generic-loc loc)
  350. (unless (equal initial-loc (point-marker))
  351. (xref-push-marker-stack initial-loc)))
  352. (call-interactively 'haskell-mode-tag-find)))
  353. ;;;###autoload
  354. (defun haskell-mode-goto-loc ()
  355. "Go to the location of the thing at point.
  356. Requires the :loc-at command from GHCi."
  357. (interactive)
  358. (let ((loc (haskell-mode-loc-at)))
  359. (when loc
  360. (haskell-mode-goto-span loc))))
  361. (defun haskell-mode-goto-span (span)
  362. "Jump to the SPAN, whatever file and line and column it needs to get there."
  363. (xref-push-marker-stack)
  364. (find-file (expand-file-name (plist-get span :path)
  365. (haskell-session-cabal-dir (haskell-interactive-session))))
  366. (goto-char (point-min))
  367. (forward-line (1- (plist-get span :start-line)))
  368. (forward-char (plist-get span :start-col)))
  369. (defun haskell-process-insert-type ()
  370. "Get the identifier at the point and insert its type.
  371. Use GHCi's :type if it's possible."
  372. (let ((ident (haskell-ident-at-point)))
  373. (when ident
  374. (let ((process (haskell-interactive-process))
  375. (query (format (if (string-match "^[_[:lower:][:upper:]]" ident)
  376. ":type %s"
  377. ":type (%s)")
  378. ident)))
  379. (haskell-process-queue-command
  380. process
  381. (make-haskell-command
  382. :state (list process query (current-buffer))
  383. :go (lambda (state)
  384. (haskell-process-send-string (nth 0 state)
  385. (nth 1 state)))
  386. :complete (lambda (state response)
  387. (cond
  388. ;; TODO: Generalize this into a function.
  389. ((or (string-match "^Top level" response)
  390. (string-match "^<interactive>" response))
  391. (message "%s" response))
  392. (t
  393. (with-current-buffer (nth 2 state)
  394. (goto-char (line-beginning-position))
  395. (insert (format "%s\n" (replace-regexp-in-string "\n$" "" response)))))))))))))
  396. (defun haskell-mode-find-def (ident)
  397. ;; TODO Check if it possible to exploit `haskell-process-do-info'
  398. "Find definition location of identifier IDENT.
  399. Uses the GHCi process to find the location. Returns nil if it
  400. can't find the identifier or the identifier isn't a string.
  401. Returns:
  402. (library <package> <module>)
  403. (file <path> <line> <col>)
  404. (module <name>)
  405. nil"
  406. (when (stringp ident)
  407. (let ((reply (haskell-process-queue-sync-request
  408. (haskell-interactive-process)
  409. (format (if (string-match "^[a-zA-Z_]" ident)
  410. ":info %s"
  411. ":info (%s)")
  412. ident))))
  413. (let ((match (string-match "-- Defined \\(at\\|in\\) \\(.+\\)$" reply)))
  414. (when match
  415. (let ((defined (match-string 2 reply)))
  416. (let ((match (string-match "\\(.+?\\):\\([0-9]+\\):\\([0-9]+\\)$" defined)))
  417. (cond
  418. (match
  419. (list 'file
  420. (expand-file-name (match-string 1 defined)
  421. (haskell-session-current-dir (haskell-interactive-session)))
  422. (string-to-number (match-string 2 defined))
  423. (string-to-number (match-string 3 defined))))
  424. (t
  425. (let ((match (string-match "`\\(.+?\\):\\(.+?\\)'$" defined)))
  426. (if match
  427. (list 'library
  428. (match-string 1 defined)
  429. (match-string 2 defined))
  430. (let ((match (string-match "`\\(.+?\\)'$" defined)))
  431. (if match
  432. (list 'module
  433. (match-string 1 defined)))))))))))))))
  434. ;;;###autoload
  435. (defun haskell-mode-jump-to-def (ident)
  436. "Jump to definition of identifier IDENT at point."
  437. (interactive
  438. (list
  439. (haskell-string-drop-qualifier
  440. (haskell-ident-at-point))))
  441. (let ((loc (haskell-mode-find-def ident)))
  442. (when loc
  443. (haskell-mode-handle-generic-loc loc))))
  444. (defun haskell-mode-handle-generic-loc (loc)
  445. "Either jump to or echo a generic location LOC.
  446. Either a file or a library."
  447. (cl-case (car loc)
  448. (file (progn
  449. (find-file (elt loc 1))
  450. (goto-char (point-min))
  451. (forward-line (1- (elt loc 2)))
  452. (goto-char (+ (line-beginning-position)
  453. (1- (elt loc 3))))))
  454. (library (message "Defined in `%s' (%s)."
  455. (elt loc 2)
  456. (elt loc 1)))
  457. (module (message "Defined in `%s'."
  458. (elt loc 1)))))
  459. (defun haskell-mode-loc-at ()
  460. "Get the location at point.
  461. Requires the :loc-at command from GHCi."
  462. (let ((pos (or (when (region-active-p)
  463. (cons (region-beginning)
  464. (region-end)))
  465. (haskell-spanable-pos-at-point)
  466. (cons (point)
  467. (point)))))
  468. (when pos
  469. (let ((reply (haskell-process-queue-sync-request
  470. (haskell-interactive-process)
  471. (save-excursion
  472. (format ":loc-at %s %d %d %d %d %s"
  473. (buffer-file-name)
  474. (progn (goto-char (car pos))
  475. (line-number-at-pos))
  476. (1+ (current-column)) ;; GHC uses 1-based columns.
  477. (progn (goto-char (cdr pos))
  478. (line-number-at-pos))
  479. (1+ (current-column)) ;; GHC uses 1-based columns.
  480. (buffer-substring-no-properties (car pos)
  481. (cdr pos)))))))
  482. (if reply
  483. (if (string-match "\\(.*?\\):(\\([0-9]+\\),\\([0-9]+\\))-(\\([0-9]+\\),\\([0-9]+\\))"
  484. reply)
  485. (list :path (match-string 1 reply)
  486. :start-line (string-to-number (match-string 2 reply))
  487. ;; ;; GHC uses 1-based columns.
  488. :start-col (1- (string-to-number (match-string 3 reply)))
  489. :end-line (string-to-number (match-string 4 reply))
  490. ;; GHC uses 1-based columns.
  491. :end-col (1- (string-to-number (match-string 5 reply))))
  492. (error (propertize reply 'face 'compilation-error)))
  493. (error (propertize "No reply. Is :loc-at supported?"
  494. 'face 'compilation-error)))))))
  495. ;;;###autoload
  496. (defun haskell-process-cd (&optional _not-interactive)
  497. ;; FIXME optional arg is not used
  498. "Change directory."
  499. (interactive)
  500. (let* ((session (haskell-interactive-session))
  501. (dir (haskell-session-prompt-set-current-dir session)))
  502. (haskell-process-log
  503. (propertize (format "Changing directory to %s ...\n" dir)
  504. 'face font-lock-comment-face))
  505. (haskell-process-change-dir session
  506. (haskell-interactive-process)
  507. dir)))
  508. (defun haskell-session-buffer-default-dir (session &optional buffer)
  509. "Try to deduce a sensible default directory for SESSION and BUFFER,
  510. of which the latter defaults to the current buffer."
  511. (or (haskell-session-get session 'current-dir)
  512. (haskell-session-get session 'cabal-dir)
  513. (if (buffer-file-name buffer)
  514. (file-name-directory (buffer-file-name buffer))
  515. "~/")))
  516. (defun haskell-session-prompt-set-current-dir (session &optional use-default)
  517. "Prompt for the current directory.
  518. Return current working directory for SESSION."
  519. (let ((default (haskell-session-buffer-default-dir session)))
  520. (haskell-session-set-current-dir
  521. session
  522. (if use-default
  523. default
  524. (haskell-utils-read-directory-name "Set current directory: " default))))
  525. (haskell-session-get session 'current-dir))
  526. (defun haskell-process-change-dir (session process dir)
  527. "Change SESSION's current directory.
  528. Query PROCESS to `:cd` to directory DIR."
  529. (haskell-process-queue-command
  530. process
  531. (make-haskell-command
  532. :state (list session process dir)
  533. :go
  534. (lambda (state)
  535. (haskell-process-send-string
  536. (cadr state) (format ":cd %s" (cl-caddr state))))
  537. :complete
  538. (lambda (state _)
  539. (haskell-session-set-current-dir (car state) (cl-caddr state))
  540. (haskell-interactive-mode-echo (car state)
  541. (format "Changed directory: %s"
  542. (cl-caddr state)))))))
  543. ;;;###autoload
  544. (defun haskell-process-cabal-macros ()
  545. "Send the cabal macros string."
  546. (interactive)
  547. (haskell-process-queue-without-filters (haskell-interactive-process)
  548. ":set -optP-include -optPdist/build/autogen/cabal_macros.h"))
  549. (defun haskell-process-do-try-info (sym)
  550. "Get info of SYM and echo in the minibuffer."
  551. (let ((process (haskell-interactive-process)))
  552. (haskell-process-queue-command
  553. process
  554. (make-haskell-command
  555. :state (cons process sym)
  556. :go (lambda (state)
  557. (haskell-process-send-string
  558. (car state)
  559. (if (string-match "^[A-Za-z_]" (cdr state))
  560. (format ":info %s" (cdr state))
  561. (format ":info (%s)" (cdr state)))))
  562. :complete (lambda (_state response)
  563. (unless (or (string-match "^Top level" response)
  564. (string-match "^<interactive>" response))
  565. (haskell-mode-message-line response)))))))
  566. (defun haskell-process-do-try-type (sym)
  567. "Get type of SYM and echo in the minibuffer."
  568. (let ((process (haskell-interactive-process)))
  569. (haskell-process-queue-command
  570. process
  571. (make-haskell-command
  572. :state (cons process sym)
  573. :go (lambda (state)
  574. (haskell-process-send-string
  575. (car state)
  576. (if (string-match "^[A-Za-z_]" (cdr state))
  577. (format ":type %s" (cdr state))
  578. (format ":type (%s)" (cdr state)))))
  579. :complete (lambda (_state response)
  580. (unless (or (string-match "^Top level" response)
  581. (string-match "^<interactive>" response))
  582. (haskell-mode-message-line response)))))))
  583. ;;;###autoload
  584. (defun haskell-mode-show-type-at (&optional insert-value)
  585. "Show type of the thing at point or within active region asynchronously.
  586. This function requires GHCi 8+ or GHCi-ng.
  587. \\<haskell-interactive-mode-map>
  588. To make this function works sometimes you need to load the file in REPL
  589. first using command `haskell-process-load-file' bound to
  590. \\[haskell-process-load-file].
  591. Optional argument INSERT-VALUE indicates that
  592. recieved type signature should be inserted (but only if nothing
  593. happened since function invocation)."
  594. (interactive "P")
  595. (let* ((pos (haskell-command-capture-expr-bounds))
  596. (req (haskell-utils-compose-type-at-command pos))
  597. (process (haskell-interactive-process))
  598. (buf (current-buffer))
  599. (pos-reg (cons pos (region-active-p))))
  600. (haskell-process-queue-command
  601. process
  602. (make-haskell-command
  603. :state (list process req buf insert-value pos-reg)
  604. :go
  605. (lambda (state)
  606. (let* ((prc (car state))
  607. (req (nth 1 state)))
  608. (haskell-utils-async-watch-changes)
  609. (haskell-process-send-string prc req)))
  610. :complete
  611. (lambda (state response)
  612. (let* ((init-buffer (nth 2 state))
  613. (insert-value (nth 3 state))
  614. (pos-reg (nth 4 state))
  615. (wrap (cdr pos-reg))
  616. (min-pos (caar pos-reg))
  617. (max-pos (cdar pos-reg))
  618. (sig (haskell-utils-reduce-string response))
  619. (res-type (haskell-utils-repl-response-error-status sig)))
  620. (cl-case res-type
  621. ;; neither popup presentation buffer
  622. ;; nor insert response in error case
  623. ('unknown-command
  624. (message "This command requires GHCi 8+ or GHCi-ng. Please read command description for details."))
  625. ('option-missing
  626. (message "Could not infer type signature. You need to load file first. Also :set +c is required, see customization `haskell-interactive-set-+c'. Please read command description for details."))
  627. ('interactive-error (message "Wrong REPL response: %s" sig))
  628. (otherwise
  629. (if insert-value
  630. ;; Only insert type signature and do not present it
  631. (if (= (length haskell-utils-async-post-command-flag) 1)
  632. (if wrap
  633. ;; Handle region case
  634. (progn
  635. (deactivate-mark)
  636. (save-excursion
  637. (delete-region min-pos max-pos)
  638. (goto-char min-pos)
  639. (insert (concat "(" sig ")"))))
  640. ;; Non-region cases
  641. (haskell-command-insert-type-signature sig))
  642. ;; Some commands registered, prevent insertion
  643. (message "Type signature insertion was prevented. These commands were registered: %s"
  644. (cdr (reverse haskell-utils-async-post-command-flag))))
  645. ;; Present the result only when response is valid and not asked
  646. ;; to insert result
  647. (haskell-command-echo-or-present response)))
  648. (haskell-utils-async-stop-watching-changes init-buffer))))))))
  649. (make-obsolete 'haskell-process-generate-tags
  650. 'haskell-mode-generate-tags
  651. "2016-03-14")
  652. (defun haskell-process-generate-tags (&optional and-then-find-this-tag)
  653. "Regenerate the TAGS table.
  654. If optional AND-THEN-FIND-THIS-TAG argument is present it is used with
  655. function `xref-find-definitions' after new table was generated."
  656. (interactive)
  657. (let ((process (haskell-interactive-process)))
  658. (haskell-process-queue-command
  659. process
  660. (make-haskell-command
  661. :state (cons process and-then-find-this-tag)
  662. :go
  663. (lambda (state)
  664. (let* ((process (car state))
  665. (cabal-dir (haskell-session-cabal-dir
  666. (haskell-process-session process)))
  667. (command (haskell-cabal--compose-hasktags-command cabal-dir)))
  668. (haskell-process-send-string process command)))
  669. :complete (lambda (state _response)
  670. (when (cdr state)
  671. (let ((tags-file-name
  672. (haskell-session-tags-filename
  673. (haskell-process-session (car state)))))
  674. (xref-find-definitions (cdr state))))
  675. (haskell-mode-message-line "Tags generated."))))))
  676. (defun haskell-process-add-cabal-autogen ()
  677. "Add cabal's autogen dir to the GHCi search path.
  678. Add <cabal-project-dir>/dist/build/autogen/ to GHCi seatch path.
  679. This allows modules such as 'Path_...', generated by cabal, to be
  680. loaded by GHCi."
  681. (unless (or (eq 'cabal-repl (haskell-process-type))
  682. (eq 'cabal-new-repl (haskell-process-type))) ;; redundant with "cabal repl"
  683. (let*
  684. ((session (haskell-interactive-session))
  685. (cabal-dir (haskell-session-cabal-dir session))
  686. (ghci-gen-dir (format "%sdist/build/autogen/" cabal-dir)))
  687. (haskell-process-queue-without-filters
  688. (haskell-interactive-process)
  689. (format ":set -i%s" ghci-gen-dir)))))
  690. ;;;###autoload
  691. (defun haskell-process-unignore ()
  692. "Unignore any ignored files.
  693. Do not ignore files that were specified as being ignored by the
  694. inferior GHCi process."
  695. (interactive)
  696. (let ((session (haskell-interactive-session))
  697. (changed nil))
  698. (if (null (haskell-session-get session 'ignored-files))
  699. (message "Nothing to unignore!")
  700. (cl-loop for file in (haskell-session-get session 'ignored-files)
  701. do
  702. (haskell-mode-toggle-interactive-prompt-state)
  703. (unwind-protect
  704. (progn
  705. (cl-case
  706. (read-event
  707. (propertize
  708. (format "Set permissions? %s (y, n, v: stop and view file)"
  709. file)
  710. 'face
  711. 'minibuffer-prompt))
  712. (?y
  713. (haskell-process-unignore-file session file)
  714. (setq changed t))
  715. (?v
  716. (find-file file)
  717. (cl-return)))
  718. (when (and changed
  719. (y-or-n-p "Restart GHCi process now? "))
  720. (haskell-process-restart)))
  721. ;; unwind
  722. (haskell-mode-toggle-interactive-prompt-state t))))))
  723. ;;;###autoload
  724. (defun haskell-session-change-target (target)
  725. "Set the build TARGET for cabal REPL."
  726. (interactive
  727. (list
  728. (completing-read "New build target: "
  729. (haskell-cabal-enum-targets (haskell-process-type))
  730. nil
  731. nil
  732. nil
  733. 'haskell-cabal-targets-history)))
  734. (let* ((session haskell-session)
  735. (old-target (haskell-session-get session 'target)))
  736. (when session
  737. (haskell-session-set-target session target)
  738. (when (not (string= old-target target))
  739. (haskell-mode-toggle-interactive-prompt-state)
  740. (unwind-protect
  741. (when (y-or-n-p "Target changed, restart haskell process? ")
  742. (haskell-process-start session)))
  743. (haskell-mode-toggle-interactive-prompt-state t)))))
  744. ;;;###autoload
  745. (defun haskell-mode-stylish-buffer ()
  746. "Apply stylish-haskell to the current buffer.
  747. Use `haskell-mode-stylish-haskell-path' to know where to find
  748. stylish-haskell executable. This function tries to preserve
  749. cursor position and markers by using
  750. `haskell-mode-buffer-apply-command'."
  751. (interactive)
  752. (haskell-mode-buffer-apply-command haskell-mode-stylish-haskell-path haskell-mode-stylish-haskell-args))
  753. (defun haskell-mode-buffer-apply-command (cmd &optional args)
  754. "Execute shell command CMD with ARGS and current buffer as input and output.
  755. Use buffer as input and replace the whole buffer with the
  756. output. If CMD fails the buffer remains unchanged."
  757. (set-buffer-modified-p t)
  758. (let* ((out-file (make-temp-file "stylish-output"))
  759. (err-file (make-temp-file "stylish-error")))
  760. (unwind-protect
  761. (let* ((_errcode
  762. (apply 'call-process-region (point-min) (point-max) cmd nil
  763. `((:file ,out-file) ,err-file)
  764. nil args))
  765. (err-file-empty-p
  766. (equal 0 (nth 7 (file-attributes err-file))))
  767. (out-file-empty-p
  768. (equal 0 (nth 7 (file-attributes out-file)))))
  769. (if err-file-empty-p
  770. (if out-file-empty-p
  771. (message "Error: %s produced no output and no error information, leaving buffer alone" cmd)
  772. ;; Command successful, insert file with replacement to preserve
  773. ;; markers.
  774. (insert-file-contents out-file nil nil nil t))
  775. (progn
  776. ;; non-null stderr, command must have failed
  777. (with-current-buffer
  778. (get-buffer-create "*haskell-mode*")
  779. (insert-file-contents err-file)
  780. (buffer-string))
  781. (message "Error: %s ended with errors, leaving buffer alone, see *haskell-mode* buffer for stderr" cmd)
  782. (with-temp-buffer
  783. (insert-file-contents err-file)
  784. ;; use (warning-minimum-level :debug) to see this
  785. (display-warning cmd
  786. (buffer-substring-no-properties (point-min) (point-max))
  787. :debug)))))
  788. (ignore-errors
  789. (delete-file err-file))
  790. (ignore-errors
  791. (delete-file out-file)))))
  792. ;;;###autoload
  793. (defun haskell-mode-find-uses ()
  794. "Find use cases of the identifier at point and highlight them all."
  795. (interactive)
  796. (let ((spans (haskell-mode-uses-at)))
  797. (unless (null spans)
  798. (highlight-uses-mode 1)
  799. (cl-loop for span in spans
  800. do (haskell-mode-make-use-highlight span)))))
  801. (defun haskell-mode-make-use-highlight (span)
  802. "Make a highlight overlay at the given SPAN."
  803. (save-window-excursion
  804. (save-excursion
  805. (haskell-mode-goto-span span)
  806. (save-excursion
  807. (highlight-uses-mode-highlight
  808. (progn
  809. (goto-char (point-min))
  810. (forward-line (1- (plist-get span :start-line)))
  811. (forward-char (plist-get span :start-col))
  812. (point))
  813. (progn
  814. (goto-char (point-min))
  815. (forward-line (1- (plist-get span :end-line)))
  816. (forward-char (plist-get span :end-col))
  817. (point)))))))
  818. (defun haskell-mode-uses-at ()
  819. "Get the locations of use cases for the ident at point.
  820. Requires the :uses command from GHCi."
  821. (let ((pos (or (when (region-active-p)
  822. (cons (region-beginning)
  823. (region-end)))
  824. (haskell-ident-pos-at-point)
  825. (cons (point)
  826. (point)))))
  827. (when pos
  828. (let ((reply (haskell-process-queue-sync-request
  829. (haskell-interactive-process)
  830. (save-excursion
  831. (format ":uses %s %d %d %d %d %s"
  832. (buffer-file-name)
  833. (progn (goto-char (car pos))
  834. (line-number-at-pos))
  835. (1+ (current-column)) ;; GHC uses 1-based columns.
  836. (progn (goto-char (cdr pos))
  837. (line-number-at-pos))
  838. (1+ (current-column)) ;; GHC uses 1-based columns.
  839. (buffer-substring-no-properties (car pos)
  840. (cdr pos)))))))
  841. (if reply
  842. (let ((lines (split-string reply "\n" t)))
  843. (cl-remove-if
  844. #'null
  845. (mapcar (lambda (line)
  846. (if (string-match "\\(.*?\\):(\\([0-9]+\\),\\([0-9]+\\))-(\\([0-9]+\\),\\([0-9]+\\))"
  847. line)
  848. (list :path (match-string 1 line)
  849. :start-line (string-to-number (match-string 2 line))
  850. ;; ;; GHC uses 1-based columns.
  851. :start-col (1- (string-to-number (match-string 3 line)))
  852. :end-line (string-to-number (match-string 4 line))
  853. ;; GHC uses 1-based columns.
  854. :end-col (1- (string-to-number (match-string 5 line))))
  855. (error (propertize line 'face 'compilation-error))))
  856. lines)))
  857. (error (propertize "No reply. Is :uses supported?"
  858. 'face 'compilation-error)))))))
  859. (defun haskell-command-echo-or-present (msg)
  860. "Present message in some manner depending on configuration.
  861. If variable `haskell-process-use-presentation-mode' is NIL it will output
  862. modified message MSG to echo area."
  863. (if haskell-process-use-presentation-mode
  864. (let ((session (haskell-process-session (haskell-interactive-process))))
  865. (haskell-presentation-present session msg))
  866. (let ((m (haskell-utils-reduce-string msg)))
  867. (message "%s" m))))
  868. (defun haskell-command-capture-expr-bounds ()
  869. "Capture position bounds of expression at point.
  870. If there is an active region then it returns region
  871. bounds. Otherwise it uses `haskell-spanable-pos-at-point` to
  872. capture identifier bounds. If latter function returns NIL this function
  873. will return cons cell where min and max positions both are equal
  874. to point."
  875. (or (when (region-active-p)
  876. (cons (region-beginning)
  877. (region-end)))
  878. (haskell-spanable-pos-at-point)
  879. (cons (point) (point))))
  880. (defun haskell-command-insert-type-signature (signature)
  881. "Insert type signature.
  882. In case of active region is present, wrap it by parentheses and
  883. append SIGNATURE to original expression. Otherwise tries to
  884. carefully insert SIGNATURE above identifier at point. Removes
  885. newlines and extra whitespace in signature before insertion."
  886. (let* ((ident-pos (or (haskell-ident-pos-at-point)
  887. (cons (point) (point))))
  888. (min-pos (car ident-pos))
  889. (sig (haskell-utils-reduce-string signature)))
  890. (save-excursion
  891. (goto-char min-pos)
  892. (let ((col (current-column)))
  893. (insert sig "\n")
  894. (indent-to col)))))
  895. (provide 'haskell-commands)
  896. ;;; haskell-commands.el ends here