Initial commit

This commit is contained in:
Mateus Pinto Rodrigues
2017-11-11 15:15:10 -02:00
commit 58c3bd6728
1202 changed files with 434097 additions and 0 deletions

View File

@@ -0,0 +1,600 @@
Haskell Mode NEWS -*- org -*-
This file uses Org mode. Some useful (default) key-bindings:
- Use "C-c C-n"/"C-c C-p" to jump to next/prev heading
- Use "<tab>" to expand/collapse nodes
- Use "<backtab>" to cycle visibility of all nodes
- Use "C-c C-o" to open links
* Changes in 16.1
- Require at least Emacs 24.3
- Implemented standalone deriving indentation
- Removed haskell-indentation-ifte-offset
- Implemented electric characters
- Added LiquidHaskell annotation highlight
- Introduced haskell-hasktags-path defcustom
- Added font lock tests for pattern synonyms
- Hardcoded haskell-ghc-supported-extensions/options
- Added multi-line input in haskell-interactive
- Added haskell-mode-stylish-haskell-path
- Added Windows CI builds
- Added syntax highlight to Yesod rules quasi quote
- Added support for line continuation in #define's
- Added support unterminated strings in indentation
- Implemented /italic/ and *bold* for haddock
- Added significant speedups in font-lock engine
- Added support for full complexity of backtick syntax
- Operators at the end or beggining of line force expression continuation in indentation
- Map haskell-mode-format-imports to C-c C-, to avoid conflict with haskell-indent
- Improve auto add dependencies in cabal
- Fix inferior-haskell warning font lock for GHC 8.0
* Changes in 13.20
- Require at least Emacs 24.1
- Honor equals on its own line in data decl
- Honor equals on separate line after guards
- Allow haskell-process-path-* to be lists
- Fontify True/False in cabal mode
- Align | with = in data declarations
- Remove haskell-package.el
- Run stylish-haskell before save
- Improved lexeme parsing in haskell-lexeme
- Add haskell-interactive-copy-to-prompt
- Remove haskell-mode-contextual-space and related var
- Make haskell-indent-region do nothing
- Add c2hs mode
- Improve hasktags handling
- Move documentation from wiki to haskell-mode manual
* Changes in 13.18
- Removed haskell-bot
- Implement shallow indentation
- Removed haskell-simple-indent
- Removed haskell-indentation show dynamic positions
- Improved indentation inside multiline strings
- Implemented font-lock for quasi quoted XML, HTML and JavaScript
- Added '{type|data} family' to font-lock-keywords-create
- Started using undercover.el for emacs lisp coverage
- Added keybinding for `haskell-cabal-visit-file`
- Added type role to font lock
- Detect and respect the comma style of a section in cabal files
* Changes in 13.16
- Improved parsing of comma lists in haskell indentation
- Declared turn-on-haskell-indentation as obsolete
- Improved QuasiQuote font lock
- Fixed indentation for Unicode symbols
- Improved completion engine
- Added support for interaction with stack ghci
- Added haskell-forward-sexp
- Added overlays to haskell load
- Made use of lexical binding for all emacs lisp sources
- Improved indentation of guards with commas
- Made three indentation modes mutually turn each other off
- Added an additional trigger for pragma suggestions
- Improved and documented `toggle-scc-at-point` in manual
* Changes in 13.14
- Add official Haskell Mode logo
- Add auto deploy for html manual
- Improve presentation mode
- Font Lock refactoring and improvements
- Properly delimit operators in prettify mode
- OPTIONS and LANGUAGE completion using ghci
- Merge hi2 (haskell-indentation attempt 2) in place of haskell-indentation
- Prompt to reconfigure when Cabal demands it
- Fontify pragmas while fontifying comments
- Operators are fontified
- Remove all mentions of cabal-dev
- Merge hsc-mode into haskell-mode
- Get type information from ghci in interaction-mode
- Prettify Haskell types in eldoc support
- Added haskell-hoogle-url
- Fix w3m-haddock in the case of no local files
* Changes in 13.12
- Added haskell-bot.el
- Added support for cabal repl build targets
- Automatically add import lines via Hoogle
- Automatically add package to cabal file
- Added w3m-haddock.el
- Added debugger mode
- Added preliminary :present support
- Added haskell-sort-imports
- Added haskell-complete-module
- Support if and multi-way if in indentation
- Add support to generate tags on windows
- Add haskell-language-extensions variable
- Improve haskell-simple-indent mode
- Improve test cases
* Changes in 13.10
- Small fix for haskell-simple-indent: Certain indentation situations
cause valname-string to be nil, which haskell-trim did not handle
gracefully (naturally, since nil != "").
- Luke Hoersten's Shnippet merged in under snippets/.
- haskell-presentation-mode is now a haskell-mode derived mode.
- Small improvement to haskell-process-do-info (works on constructors
now and underscored names).
- Add haskell-indent-spaces configuration variable.
- The command string to run cabal commands is slightly more
configurable. See: C-h f haskell-process-do-cabal-format-string
* Changes in 13.8
See also [[https://github.com/haskell/haskell-mode/compare/v13.07...v13.08][detailed Git history]].
- Make `haskell-simple-indent-mode' a proper minor mode with `SInd` as
mode-line lighter
- Support popular "λ> " prompt in inf-haskell by default
- Hide internal `*print-haskell-mode*' buffers
(used when `haskell-interactive-mode-eval-mode' is active)
- Add tab-completion support for haskell-interactive-mode
(requires `:complete' command support in GHCi)
- Add support to `haskell-process-do-info` to perform `:browse!` query
on module name when called on import statement line
- `haskell-decl-scan-mode':
- New customize group `haskell-decl-scan'
- New flag `haskell-decl-scan-bindings-as-variables' for controlling
whether to put value bindings into the "Variables" category.
- New flag `haskell-decl-scan-add-to-menubar' for controlling
whether to add "Declarations" menu entry to menu bar.
- New manual section node `(haskell-mode)haskell-decl-scan-mode'
- Add support for [[http://www.haskell.org/ghc/docs/latest/html/users_guide/syntax-extns.html#lambda-case][LambdaCase]] syntax extension to `haskell-indentation`
- Change `haskell-indentation-mode' to never jump back a whole line
when pressing DEL. The old behavior can be restored by setting
`haskell-indentation-delete-backward-jump-line' to t
- New convenience function `haskell-cabal-visit-file' for locating and
visiting most likely `.cabal` file associated with current buffer
- Add support for [[http://www.haskell.org/ghc/docs/latest/html/users_guide/syntax-extns.html#package-import][PackageImports]] and [[http://www.haskell.org/ghc/docs/latest/html/users_guide/syntax-extns.html#safe-imports-ext][SafeHaskell]] syntax extensions to
`haskell-decl-scan-mode' parser
- Add `turn-{on,off}-haskell-doc' commands as aliases for the existing
`turn-{on,off}-haskell-doc-mode' commands
- Add support for "cabal repl" process type to `haskell-interactive-mode'
- Add new Haskell compilation sub-mode and associated `haskell-compile'
command
* Changes in 13.7
See also [[https://github.com/haskell/haskell-mode/compare/v13.06...v13.07][detailed Git history]].
- Convert NEWS (this file) to Org mode style and include NEWS file in
package and add command for visiting NEWS file
(M-x haskell-mode-view-news)
- Officially drop support for versions prior to Emacs 23
- New work-in-progress Info manual for haskell-mode
- Remove deprecated `haskell-{hugs,ghci}' modules
- Font-locking changes:
- Remove deprecated `turn-on-haskell-font-lock` function
- Improve font-locking of type-signatures in presence of newlines
- Use `font-lock-preprocessor-face' instead of the previously used
`font-lock-warning-face` for CPP directives
- Use `font-lock-warning-face` instead of the previously used
`font-lock-preprocessor-face` for Git merge conflict annotations.
- Improvements to `haskell-move-nested' module:
- Add support for operating on active regions
- New interactive commands `haskell-move-nested-{left,right}` which
support numeric prefix arguments for controlling the amount of
shifting to apply.
- Add `haskell-unicode-input-method.el` to distribution
(enable with `turn-on-haskell-unicode-input-method`)
- Fix all byte-compilation warnings
- Build-system:
- For in-place installation, `haskell-site-file.el' is renamed
to `haskell-mode-autoloads.el`
- Auto-generate ELPA compatible README file by extracting header of
haskell-mode.el
- New "make check" target
- Add Travis-CI build jobs for testing byte-compilation with
multiple Emacs versions
- Reorganize customize settings
- Add new convenience function for browsing all Haskell Mode settings
(M-x haskell-customize)
- Add `:link' keywords pointing to the new Info manual
- Add `:group' keywords to modes to make (M-x customize-mode) work
- Create new customization groups `haskell-interactive' and `inferior-haskell'
to clean up namespace
- Create new customization group `ghc-core` containing the two new
customization variables `ghc-core-program` and `ghc-core-program-args`.
- Improvements to haskell-interactive-mode
- Add support for deleting compile messages superseded by recompile/reloads
(M-x customize-variable RET haskell-interactive-mode-delete-superseded-errors)
- Fix `C-u M-x haskell-process-do-type` inserting bad signatures
- Integrate with Emacs' `next-error` subsystem
- Add "C-c C-f" binding to REPL keymap for enabling `next-error-follow-minor-mode'
- Add support for `-ferror-spans`-style compile messages
- Add `-ferror-spans` as default for `haskell-process-args-ghci`
- Add optional argument to
`haskell-session-{all,installed,project}-modules' to suppress
session-creation. This is useful for yasnippet usage, see commit
517fd7e for an example.
- Change default for `haskell-process-path-ghci` to a static "ghci"
- Fix `haskell-interactive-switch` not selecting the REPL window
- Make `*haskell-process-log*` buffer configurable
(controlled via new `haskell-process-log` customize option)
* Changes in 13.6
See also [[https://github.com/haskell/haskell-mode/compare/2_9_1...v13.06][detailed Git history]].
- Switch to new versioning scheme
- Switch to MELPA/Marmalade based packaging
- Cleanup/refactor build-system
- Enhance `M-x haskell-version` to report more detailed versioning
information
- Make haskell-interactive-mode emulate comint/eshell history navigation
(see commit 0e96843 for more details)
- Improvements to haskell-interactive-mode
- Improve killing/restarting haskell-interactive sessions
- Improve directory prompting and resolution
- Fix redundant-import suggest trigger to support qualified imports
- Detect all abbreviations of an user-inputted ":quit"
- Fix regexps for recent GHC 7.x compiler messages
- Customizable commandline args for GHCi
(M-x customize-variable RET haskell-process-args-ghci)
- New command to load or reload via prefix argument
(M-x haskell-process-load-or-reload)
- Fix haskell-interactive-mode prompt detection
- Add cabal-ghci as supported process mode
- Add a customization option for the visibility of multi-line errors
(M-x customize-variable RET haskell-interactive-mode-hide-multi-line-errors)
- Add forward declarations to reduce Elisp bytecompile warnings
- Improvements to `haskell-indentation`
- Add support for the UnicodeSyntax tokens `→`, `←`, and `∷`.
- Indent "=" following data/type/newtype declarations.
- Align "->"/"→" arrows in types under "::"/"∷"
- Make customizable whether "<backspace>" deletes indentation too
(via `haskell-indentation-delete-backward-indentation` and
`haskell-indentation-delete-indentation`)
- Properly indent 'rec' keyword, same as 'mdo'
- Minor optimizations.
- Add support for "'"-prefixed constructors (-> DataKinds) to font-locking
- New experimental haskell session menu mode (M-x haskell-menu)
- Various minor cleanups/fixes/improvements...
* Changes in 2.9.1
See also [[https://github.com/haskell/haskell-mode/compare/2_9_0...2_9_1][detailed Git history]].
- Bugfix release adding missing autoload declaration
* Changes in 2.9.0
See also [[https://github.com/haskell/haskell-mode/compare/2_8_0...2_9_0][detailed Git history]].
- This is the first release after haskell-mode was migrated to GitHub
- New experimental `haskell-interactive-mode' module implementing a
new REPL interaction mode for GHCi sessions to eventually replace
the existing "inf-haskell" mode.
- New `haskell-process-cabal' command for interaction with cabal-install
- New `haskell-checkers' module
- Update haskell-cabal-mode font-lock keywords
- Improve scrolling of hoogle output (haskell-mode.el)
- Derive `haskell-mode` from `prog-mode` for Emacs 24+
- Add new binding for "<backtab>" to haskell-mode's keymap which
unindents current line
- New modules `haskell-navigate-imports`, `haskell-sort-imports' and
`haskell-align-imports' for operating on module import lines in
Haskell source code
- Add new binding for "C-c C-." to haskell-mode's keymap to sort and
realign Haskell module imports
- Add new binding for "C-c i" to haskell-mode's keymap to jump back and
forth from/to the current Haskell module's module import section.
- New `inferior-haskell-kind' function for querying kind via GHCi's ":kind"
- New `inferior-haskell-send-decl' for sending declarations to GHCi
(bound to "C-x C-d" by default)
- Add new `haskell-doc-use-inf-haskell` customization variable
- Add support for bird-style literate haskell editing and a new
related customization variable
`haskell-indentation-birdtrack-extra-space'
- Font locking improvements
- Add support for Git's merge annotation
(with `font-lock-preprocessor-face')
- Improve `import', `foreign import' and `foreign export' font
locking
- Add support for `rec', `proc' and `mdo` as keywords
- Make whitespace within `-- |' and `{- |' optional when possible
- New `haskell-move-nested` module providing utilities for
interactively {in,de}denting nested "hanging" blocks.
- Add stylish-haskell support
(enable via `haskell-stylish-on-save` customization variable)
- Add support for generating tags on save
(enable via `haskell-tags-on-save' customization variable)
- Set sensible dabbrev defaults in haskell-mode
- Added `SCC` pragma insert/delete commands
(`haskell-mode-insert-scc-at-point` and `haskell-mode-kill-scc-at-point')
- New experimental `haskell-mode-contextual-space' command
- And a couple more cleanups/fixes/improvements...
* Changes in 2.8.0 (since 2.7.0)
See also [[https://github.com/haskell/haskell-mode/compare/2_7_0...2_8_0][detailed Git history]].
- Minimal indentation support for arrow syntax
- Avoid opening a new inf-haskell window if one is already visible.
Windows on other virtual desktops or iconified frames don't count.
- Force comint-process-echoes to nil
- Autolaunch haskell-mode for files starting with #!/usr/bin/runghc
and similar
- Added minimal major mode for parsing GHC core files, courtesy of Johan Tibell.
There is a corresponding Haskell menu entry.
- Allow configuration of where-clause indentation; M-x customize-group
haskell-indentation.
* Changes since 2.6.4
- fill-paragraph (M-q) now only affects comments, and correctly
handles Haddock commentary. adaptive-fill-mode is turned off, as it
was interfering.
- Yet more unicode symbols
- Better support for unicode encoding of haskell source files
- mdo correctly indented
- Indentation fixes, fixes to the fixes, and fixes to the fixes to the
fixes
- New command: M-x haskell-check, calls (by default) hlint on the
current file. Also bound to C-c C-v.
You can also use the flymake minor mode with this.
* Changes since 2.5.1
- Parser corrections for haskell-indentation and haskell-decl-scan
- haskell-indentation: Pressing tab in the rightmost position now
moves to the leftmost, by default with a warning.
- Typo fix: One haskell-indentation variable had ended up in the
haskell-ntation customize group.
- haskell-hoogle aliased to hoogle, haskell-hayoo aliased to hayoo
- Courtesy of Alex Ott:
- Additional unicode symbols for font-lock-symbols: () == /= >= <= !! && || sqrt
- M-x haskell-hayoo search added, opens using browse-url
- Bug-fix for inferior-haskell-type
- If haskell-indentation errors out, it now fail-safes to inserting
a literal newline or deleting one character, for return and
backspace respectively.
* Changes since 2.4:
- haskell-indentation, a new minor mode for indentation.
* Changes since 2.3:
- Update license to GPLv3.
- New derived major mode for .hsc files.
- Removed the C-c C-r binding to reload a file. You can still call
inferior-haskell-reload-file (and/or bind it to your favorite key,
including C-c C-r) or you can now use C-u C-c C-l.
- C-c C-d looks up the symbol at point in the Haddock docs.
- Haddock comments are highlighted with font-lock-doc-face if it exists.
- Use `tex' rather than `latex' for haskell-literate.
- inf-haskell.el tries to find the root of the module hierarchy to determine
the root of a project (either by looking for a Cabal file or relying on
the `module' declaration line). If all works well, this will make C-c C-l
automatically switch to the root dir, so that dependencies in other
directories are automatically found. If it doesn't, complain and/or set
inferior-haskell-find-project-root to nil.
- The new command haskell-hoogle helps you query Hoogle from Emacs.
* Changes since 2.2:
- Trivial support for Cabal package description files.
- Minor bug fixes.
* Changes since 2.1:
- There are now commands to find type and info of identifiers by querying an
inferior haskell process. Available under C-c C-t, C-c C-i, and C-c M-.
- Indentation now looks back further, until a line that has no indentation.
To recover the earlier behavior of stopping at the first empty line
instead, configure haskell-indent-look-past-empty-line.
- inf-haskell can wait until a file load completes and jump directly to the
first error, like haskell-ghci and haskell-hugs used to do. See the var
inferior-haskell-wait-and-jump.
* Changes since 2.0:
- inf-haskell uses ghci if hugs is absent.
- Fix up some binding conflicts (C-c C-o in haskell-doc)
- Many (hopefully minor) changes to the indentation.
- New symbols in haskell-font-lock-symbols-alist.
* Changes since 1.45:
- keybindings C-c <char> have been replaced by C-c C-<char> so as not
to collide with minor modes.
- The following modules are now automatically activated without having to
add anything to haskell-mode-hook:
haskell-font-lock (just turn on global-font-lock-mode).
haskell-decl-scan (just bind `imenu' to some key).
- In recent Emacsen, haskell-doc hooks into eldoc-mode.
- haskell-hugs and haskell-ghci are superceded by inf-haskell.
- Indentation rules have been improved when using layout inside parens/braces.
- Symbols like -> and \ can be displayed as actual arrows and lambdas.
See haskell-font-lock-symbols.
- Tweaks to the font-lock settings. Among other things paren-matching
with things like \(x,y) should work correctly now.
- New maintainer <monnier@gnu.org>.

View File

@@ -0,0 +1,18 @@
This is the file .../info/dir, which contains the
topmost node of the Info hierarchy, called (dir)Top.
The first time you invoke Info you start off looking at this node.

File: dir, Node: Top This is the top of the INFO tree
This (the Directory node) gives a menu of major topics.
Typing "q" exits, "?" lists all Info commands, "d" returns here,
"h" gives a primer for first-timers,
"mEmacs<Return>" visits the Emacs manual, etc.
In Emacs, you can click mouse button 2 on a menu item or cross reference
to select it.
* Menu:
Emacs
* Haskell Mode: (haskell-mode). Haskell Development Environment for Emacs(en)

View File

@@ -0,0 +1,123 @@
;;; ghc-core.el --- Syntax highlighting module for GHC Core -*- lexical-binding: t -*-
;; Copyright (C) 2010 Johan Tibell
;; Author: Johan Tibell <johan.tibell@gmail.com>
;; This file is not part of GNU Emacs.
;; 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:
;; Purpose:
;;
;; To make it easier to read GHC Core output by providing highlighting
;; and removal of commonly ignored annotations.
;;; Code:
(require 'haskell-mode)
(require 'haskell-font-lock)
;;;###autoload
(defgroup ghc-core nil
"Major mode for viewing pretty printed GHC Core output."
:link '(custom-manual "(haskell-mode)")
:group 'haskell
:prefix "ghc-core-")
(defcustom ghc-core-program
"ghc"
"Name of the GHC executable (excluding any arguments)."
:type 'string
:group 'ghc-core)
(defcustom ghc-core-program-args
'("-O2")
"Additional options to be passed to GHC when generating core output.
GHC (see variable `ghc-core-program') is invoked with the basic
command line options \"-ddump-simpl -c <source-file>\"
followed by the additional options defined here.
The following `-ddump-simpl` options might be of interest:
- `-dsuppress-all'
- `-dsuppress-uniques'
- `-dsuppress-idinfo'
- `-dsuppress-module-prefixes'
- `-dsuppress-type-signatures'
- `-dsuppress-type-applications'
- `-dsuppress-coercions'
See `M-x manual-entry RET ghc' for more details."
:type '(repeat (string :tag "Argument"))
:group 'ghc-core)
(define-obsolete-variable-alias 'ghc-core-create-options 'ghc-core-program-args
"haskell-mode 13.7")
(defun ghc-core-clean-region (start end)
"Remove commonly ignored annotations and namespace prefixes
in the region between START and END."
(interactive "r")
(save-restriction
(narrow-to-region start end)
(goto-char (point-min))
(while (search-forward-regexp "GHC\.[^\.]*\." nil t)
(replace-match "" nil t))
(goto-char (point-min))
(while (flush-lines "^ *GblId *$" nil))
(goto-char (point-min))
(while (flush-lines "^ *LclId *$" nil))
(goto-char (point-min))
(while (flush-lines (concat "^ *\\[\\(?:Arity [0-9]+\\|NoCafRefs\\|"
"Str: DmdType\\|Worker \\)"
"\\([^]]*\\n?\\).*\\] *$") nil))
(goto-char (point-min))
(while (search-forward "Main." nil t) (replace-match "" nil t))))
(defun ghc-core-clean-buffer ()
"Remove commonly ignored annotations and namespace prefixes
in the current buffer."
(interactive)
(ghc-core-clean-region (point-min) (point-max)))
;;;###autoload
(defun ghc-core-create-core ()
"Compile and load the current buffer as tidy core."
(interactive)
(save-buffer)
(let* ((core-buffer (generate-new-buffer "ghc-core"))
(neh (lambda () (kill-buffer core-buffer))))
(add-hook 'next-error-hook neh)
(apply #'call-process ghc-core-program nil core-buffer nil
"-ddump-simpl" "-c" (buffer-file-name) ghc-core-program-args)
(display-buffer core-buffer)
(with-current-buffer core-buffer
(ghc-core-mode))
(remove-hook 'next-error-hook neh)))
;;;###autoload
(add-to-list 'auto-mode-alist '("\\.hcr\\'" . ghc-core-mode))
;;;###autoload
(add-to-list 'auto-mode-alist '("\\.dump-simpl\\'" . ghc-core-mode))
;;;###autoload
(define-derived-mode ghc-core-mode haskell-mode "GHC-Core"
"Major mode for GHC Core files.")
(provide 'ghc-core)
;;; ghc-core.el ends here

Binary file not shown.

View File

@@ -0,0 +1,66 @@
;;; ghci-script-mode.el --- GHCi scripts major mode -*- lexical-binding: t -*-
;; Copyright (c) 2014 Chris Done. All rights reserved.
;; 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 this program. If not, see <http://www.gnu.org/licenses/>.
;;; Code:
(require 'haskell)
(defvar ghci-script-mode-keywords
;; The comment syntax can't be described simply in syntax-table.
;; We could use font-lock-syntactic-keywords, but is it worth it?
'(("^[ \t]*--.*" . font-lock-comment-face)
("^ *\\([^ \t:]+\\):" (1 font-lock-keyword-face))
("^:[a-z{]+ *\\+" . font-lock-keyword-face)
("^:[a-z{]+ " . font-lock-keyword-face)))
;;;###autoload
(define-derived-mode ghci-script-mode text-mode "GHCi-Script"
"Major mode for working with .ghci files."
(setq-local adaptive-fill-mode nil)
(setq-local comment-start "-- ")
(setq-local comment-padding 0)
(setq-local comment-start-skip "[-{]-[ \t]*")
(setq-local comment-end "")
(setq-local comment-end-skip "[ \t]*\\(-}\\|\\s>\\)")
(setq-local font-lock-defaults '(ghci-script-mode-keywords t t nil nil))
(setq-local indent-tabs-mode nil)
(setq-local tab-width 8)
(when (boundp 'electric-indent-inhibit)
(setq electric-indent-inhibit t))
(setq-local dabbrev-case-fold-search nil)
(setq-local dabbrev-case-distinction nil)
(setq-local dabbrev-case-replace nil)
(setq-local dabbrev-abbrev-char-regexp "\\sw\\|[.]")
(setq haskell-literate nil))
;;;###autoload
(add-to-list 'auto-mode-alist '("\\.ghci\\'" . ghci-script-mode))
(define-key ghci-script-mode-map (kbd "C-c C-l") 'ghci-script-mode-load)
(defun ghci-script-mode-load ()
"Load the current script file into the GHCi session."
(interactive)
(let ((buffer (haskell-session-interactive-buffer (haskell-session)))
(filename (buffer-file-name)))
(save-buffer)
(with-current-buffer buffer
(set-marker haskell-interactive-mode-prompt-start (point-max))
(haskell-interactive-mode-run-expr
(concat ":script " filename)))))
(provide 'ghci-script-mode)

Binary file not shown.

View File

@@ -0,0 +1,231 @@
;;; haskell-align-imports.el --- Align the import lines in a Haskell file -*- lexical-binding: t -*-
;; Copyright (C) 2010 Chris Done
;; Author: Chris Done <chrisdone@gmail.com>
;; This file is not part of GNU Emacs.
;; This program 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 of
;; the License, or (at your option) any later version.
;; This program 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 this program. If not, see
;; <http://www.gnu.org/licenses/>.
;;; Commentary:
;; Consider the following imports list:
;;
;; import One
;; import Two as A
;; import qualified Three
;; import qualified Four as PRELUDE
;; import Five (A)
;; import Six (A,B)
;; import qualified Seven (A,B)
;; import "abc" Eight
;; import "abc" Nine as TWO
;; import qualified "abc" Ten
;; import qualified "defg" Eleven as PRELUDE
;; import "barmu" Twelve (A)
;; import "zotconpop" Thirteen (A,B)
;; import qualified "z" Fourteen (A,B)
;; import Fifteen hiding (A)
;; import Sixteen as TWO hiding (A)
;; import qualified Seventeen hiding (A)
;; import qualified Eighteen as PRELUDE hiding (A)
;; import "abc" Nineteen hiding (A)
;; import "abc" Twenty as TWO hiding (A)
;;
;; When haskell-align-imports is run within the same buffer, the
;; import list is transformed to:
;;
;; import "abc" Eight
;; import qualified Eighteen as PRELUDE hiding (A)
;; import qualified "defg" Eleven as PRELUDE
;; import Fifteen hiding (A)
;; import Five (A)
;; import qualified Four as PRELUDE
;; import qualified "z" Fourteen (A,B)
;; import "abc" Nine as TWO
;; import "abc" Nineteen hiding (A)
;; import One
;; import qualified Seven (A,B)
;; import qualified Seventeen hiding (A)
;; import Six (A,B)
;; import Sixteen as TWO hiding (A)
;; import qualified "abc" Ten
;; import "zotconpop" Thirteen (A,B)
;; import qualified Three
;; import "barmu" Twelve (A)
;; import "abc" Twenty as TWO hiding (A)
;; import Two as A
;;
;; If you want everything after module names to be padded out, too,
;; customize `haskell-align-imports-pad-after-name', and you'll get:
;;
;; import One
;; import Two as A
;; import qualified Three
;; import qualified Four as PRELUDE
;; import Five (A)
;; import Six (A,B)
;; import qualified Seven (A,B)
;; import "abc" Eight
;; import "abc" Nine as TWO
;; import qualified "abc" Ten
;; import qualified "defg" Eleven as PRELUDE
;; import "barmu" Twelve (A)
;; import "zotconpop" Thirteen (A,B)
;; import qualified "z" Fourteen (A,B)
;; import Fifteen hiding (A)
;; import Sixteen as TWO hiding (A)
;; import qualified Seventeen hiding (A)
;; import qualified Eighteen as PRELUDE hiding (A)
;; import "abc" Nineteen hiding (A)
;; import "abc" Twenty as TWO hiding (A)
;;; Code:
(require 'cl-lib)
(defvar haskell-align-imports-regexp
(concat "^\\(import[ ]+\\)"
"\\(qualified \\)?"
"[ ]*\\(\"[^\"]*\" \\)?"
"[ ]*\\([A-Za-z0-9_.']+\\)"
"[ ]*\\([ ]*as [A-Z][^ ]*\\)?"
"[ ]*\\((.*)\\)?"
"\\([ ]*hiding (.*)\\)?"
"\\( -- .*\\)?[ ]*$")
"Regex used for matching components of an import.")
(defcustom haskell-align-imports-pad-after-name
nil
"Pad layout after the module name also."
:type 'boolean
:group 'haskell-interactive)
;;;###autoload
(defun haskell-align-imports ()
"Align all the imports in the buffer."
(interactive)
(when (haskell-align-imports-line-match)
(save-excursion
(goto-char (point-min))
(let* ((imports (haskell-align-imports-collect))
(padding (haskell-align-imports-padding imports)))
(mapc (lambda (x)
(goto-char (cdr x))
(delete-region (point) (line-end-position))
(insert (haskell-align-imports-chomp
(haskell-align-imports-fill padding (car x)))))
imports))))
nil)
(defun haskell-align-imports-line-match ()
"Try to match the current line as a regexp."
(let ((line (buffer-substring-no-properties (line-beginning-position)
(line-end-position))))
(if (string-match "^import " line)
line
nil)))
(defun haskell-align-imports-collect ()
"Collect a list of mark / import statement pairs."
(let ((imports '()))
(while (not (or (equal (point) (point-max)) (haskell-align-imports-after-imports-p)))
(let ((line (haskell-align-imports-line-match-it)))
(when line
(let ((match
(haskell-align-imports-merge-parts
(cl-loop for i from 1 to 8
collect (haskell-align-imports-chomp (match-string i line))))))
(setq imports (cons (cons match (line-beginning-position))
imports)))))
(forward-line))
imports))
(defun haskell-align-imports-merge-parts (l)
"Merge together parts of an import statement that shouldn't be separated."
(let ((parts (apply #'vector l))
(join (lambda (ls)
(cl-reduce (lambda (a b)
(concat a
(if (and (> (length a) 0)
(> (length b) 0))
" "
"")
b))
ls))))
(if haskell-align-imports-pad-after-name
(list (funcall join (list (aref parts 0)
(aref parts 1)
(aref parts 2)))
(aref parts 3)
(funcall join (list (aref parts 4)
(aref parts 5)
(aref parts 6)))
(aref parts 7))
(list (funcall join (list (aref parts 0)
(aref parts 1)
(aref parts 2)))
(funcall join (list (aref parts 3)
(aref parts 4)
(aref parts 5)
(aref parts 6)
(aref parts 7)))))))
(defun haskell-align-imports-chomp (str)
"Chomp leading and tailing whitespace from STR."
(if str
(replace-regexp-in-string "\\(^[[:space:]\n]*\\|[[:space:]\n]*$\\)" ""
str)
""))
(defun haskell-align-imports-padding (imports)
"Find the padding for each part of the import statements."
(if (null imports)
imports
(cl-reduce (lambda (a b) (cl-mapcar #'max a b))
(mapcar (lambda (x) (mapcar #'length (car x)))
imports))))
(defun haskell-align-imports-fill (padding line)
"Fill an import line using the padding worked out from all statements."
(mapconcat #'identity
(cl-mapcar (lambda (pad part)
(if (> (length part) 0)
(concat part (make-string (- pad (length part)) ? ))
(make-string pad ? )))
padding
line)
" "))
(defun haskell-align-imports-line-match-it ()
"Try to match the current line as a regexp."
(let ((line (buffer-substring-no-properties (line-beginning-position)
(line-end-position))))
(if (string-match haskell-align-imports-regexp line)
line
nil)))
(defun haskell-align-imports-after-imports-p ()
"Are we after the imports list?"
(save-excursion
(goto-char (line-beginning-position))
(let ((case-fold-search nil))
(not (not (search-forward-regexp "\\( = \\|\\<instance\\>\\| :: \\| ∷ \\)"
(line-end-position) t 1))))))
(provide 'haskell-align-imports)
;;; haskell-align-imports.el ends here

View File

@@ -0,0 +1,207 @@
;; haskell-c2hs.el --- -*- lexical-binding: t; -*-
;; Copyright (C) 2016 Sergey Vinokurov
;;
;; Author: Sergey Vinokurov <serg.foo@gmail.com>
;; Created: Monday, 7 March 2016
;; This program 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 of the License, or
;; (at your option) any later version.
;; This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; This mode is mostly intended for highlighting {#...#} hooks.
;;
;; Quick setup:
;; (autoload 'haskell-c2hs-mode "haskell-c2hs-mode" nil t)
;; (add-to-list 'auto-mode-alist '("\\.chs\\'" . haskell-c2hs-mode))
;;
(require 'haskell-mode)
(require 'haskell-font-lock)
(require 'haskell-utils)
;;;###autoload
(add-to-list 'auto-mode-alist '("\\.chs\\'" . haskell-c2hs-mode))
(defface haskell-c2hs-hook-pair-face
'((t (:inherit 'font-lock-preprocessor-face)))
"Face for highlighting {#...#} pairs."
:group 'haskell)
(defface haskell-c2hs-hook-name-face
'((t (:inherit 'font-lock-keyword-face)))
"Face for highlighting c2hs hook names."
:group 'haskell)
(defvar haskell-c2hs-font-lock-keywords
`((,(eval-when-compile
(let* ((ws '(any ?\s ?\t ?\n ?\r))
(anychar '(or (not (any ?#))
(seq "#"
(not (any ?\})))))
(any-nonquote '(or (not (any ?# ?\"))
(seq "#"
(not (any ?\} ?\")))))
(cid '(seq (any (?a . ?z) (?A . ?Z) ?_)
(* (any (?a . ?z) (?A . ?Z) (?0 . ?9) ?_))))
(hsid-type '(seq (? "'")
(any (?A . ?Z))
(* (any (?a . ?z) (?A . ?Z) (?0 . ?9) ?_ ?'))))
(equals-str-val `(seq (* ,ws)
"="
(* ,ws)
"\""
(* ,any-nonquote)
"\"")))
(eval
`(rx
(seq
(group-n 1 "{#")
(* ,ws)
(or (seq (group-n 2
"import"
(opt (+ ,ws)
"qualified"))
(+ ,ws))
(seq (group-n 2
"context")
(opt (+ ,ws)
(group-n 3
"lib")
,equals-str-val)
(opt (+ ,ws)
(group-n 4
"prefix")
,equals-str-val)
(opt (+ ,ws)
(group-n 5
"add"
(+ ,ws)
"prefix")
,equals-str-val))
(seq (group-n 2
"type")
(+ ,ws)
,cid)
(seq (group-n 2
"sizeof")
(+ ,ws)
,cid)
(seq (group-n 2
"enum"
(+ ,ws)
"define")
(+ ,ws)
,cid)
;; TODO: vanilla enum fontification is incomplete
(seq (group-n 2
"enum")
(+ ,ws)
,cid
(opt (+ ,ws)
(group-n 3
"as")))
;; TODO: fun hook highlighting is incompelete
(seq (group-n 2
(or "call"
"fun")
(opt (+ ,ws)
"pure")
(opt (+ ,ws)
"unsafe"))
(+ ,ws)
,cid
(opt (+ ,ws)
(group-n 3
"as")
(opt (+ ,ws)
(group-n 8
"^"))))
(group-n 2
"get")
(group-n 2
"set")
(seq (group-n 2
"pointer")
(or (seq (* ,ws)
(group-n 3 "*")
(* ,ws))
(+ ,ws))
,cid
(opt (+ ,ws)
(group-n 4 "as")
(+ ,ws)
,hsid-type)
(opt (+ ,ws)
(group-n 5
(or "foreign"
"stable")))
(opt
(or (seq (+ ,ws)
(group-n 6
"newtype"))
(seq (* ,ws)
"->"
(* ,ws)
,hsid-type)))
(opt (+ ,ws)
(group-n 7
"nocode")))
(group-n 2
"class")
(group-n 2
"alignof")
(group-n 2
"offsetof")
(seq (group-n 2
"const")
(+ ,ws)
,cid)
(seq (group-n 2
"typedef")
(+ ,ws)
,cid
(+ ,ws)
,hsid-type)
(group-n 2
"nonGNU")
;; TODO: default hook not implemented
)
(* ,anychar)
(group-n 9 "#}"))))))
;; Override highlighting for pairs in order to always distinguish them.
(1 'haskell-c2hs-hook-pair-face t)
(2 'haskell-c2hs-hook-name-face)
;; Make matches lax, i.e. do not signal error if nothing
;; matched.
(3 'haskell-c2hs-hook-name-face nil t)
(4 'haskell-c2hs-hook-name-face nil t)
(5 'haskell-c2hs-hook-name-face nil t)
(6 'haskell-c2hs-hook-name-face nil t)
(7 'haskell-c2hs-hook-name-face nil t)
(8 'font-lock-negation-char-face nil t)
;; Override highlighting for pairs in order to always distinguish them.
(9 'haskell-c2hs-hook-pair-face t))
,@(haskell-font-lock-keywords)))
;;;###autoload
(define-derived-mode haskell-c2hs-mode haskell-mode "C2HS"
"Mode for editing *.chs files of the c2hs haskell tool."
(setq-local font-lock-defaults
(cons 'haskell-c2hs-font-lock-keywords
(cdr font-lock-defaults))))
(provide 'haskell-c2hs)
;; haskell-c2hs.el ends here

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -0,0 +1,114 @@
;;; haskell-collapse.el --- Collapse expressions -*- lexical-binding: t -*-
;; Copyright (c) 2014 Chris Done. All rights reserved.
;; Copyright (c) 2017 Vasantha Ganesh Kanniappan <vasanthaganesh.k@tuta.io>.
;; 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 this program. If not, see <http://www.gnu.org/licenses/>.
;;; Code:
(require 'hideshow)
;;; TODO:
;;; -> Make it work for braces
(defun haskell-hide-toggle ()
"Toggle visibility of existing forms at point. "
(interactive)
(hs-minor-mode 1)
(save-excursion
(let* ((modified (buffer-modified-p))
(inhibit-read-only t)
(position (haskell-indented-block))
(beg (car position))
(end (cdr position)))
(if (and beg end)
(if (overlays-in beg end)
(hs-discard-overlays beg end)
(hs-make-overlay beg end 'code)))
(set-buffer-modified-p modified))))
(defun haskell-blank-line-p ()
"Returns `t' if line is empty or composed only of whitespace."
(save-excursion
(beginning-of-line)
(= (point-at-eol)
(progn (skip-chars-forward "[:blank:]") (point)))))
(defun haskell-indented-block ()
"return (start-of-indentation . end-of-indentation)"
(let ((cur-indent (current-indentation))
(nxt-line-indent (haskell-next-line-indentation 1))
(prev-line-indent (haskell-next-line-indentation -1))
(beg-of-line (save-excursion (end-of-line)
(point))))
(cond ((and (= cur-indent 0)
(= nxt-line-indent 0)) nil)
((haskell-blank-line-p) nil)
((> nxt-line-indent cur-indent)
(cons beg-of-line
(haskell-find-line-with-indentation '> 1)))
((or (= nxt-line-indent cur-indent)
(<= prev-line-indent cur-indent))
(cons (haskell-find-line-with-indentation '>= -1)
(haskell-find-line-with-indentation '>= 1)))
(t nil))))
(defun haskell-next-line-indentation (dir)
"returns (integer) indentation of the next if dir=1, previous line
indentation if dir=-1"
(save-excursion
(progn
(while (and (zerop (forward-line dir))
(haskell-blank-line-p)))
(current-indentation))))
(defun haskell-find-line-with-indentation (comparison direction)
"comparison is >= or >, direction if 1 finds forward, if -1 finds backward"
(save-excursion
(let ((start-indent (current-indentation)))
(progn
(while (and (zerop (forward-line direction))
(or (haskell-blank-line-p)
(funcall comparison (current-indentation) start-indent))))
(when (= direction 1) (forward-line -1))
(end-of-line)
(point)))))
(defun haskell-hide-toggle-all ()
"hides all top level functions"
(interactive)
(save-excursion
(goto-char (point-max))
(while (zerop (forward-line -1))
(goto-char (point-at-bol))
(when (= (current-indentation) 0) (haskell-hide-toggle)))))
(defvar haskell-collapse-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "C-c @ C-c") 'haskell-hide-toggle)
(define-key map (kbd "C-c @ C-M-c") 'haskell-hide-toggle-all)
(define-key map (kbd "C-c @ C-M-s") 'haskell-hide-toggle-all)
(define-key map (kbd "C-c @ C-M-h") 'haskell-hide-toggle-all)
map)
"Keymap for using `haskell-collapse-mode'.")
;;;###autoload
(define-minor-mode haskell-collapse-mode
"Minor mode to collapse and expand haskell expressions"
:init-value nil
:lighter " Haskell-Collapse"
:keymap haskell-collapse-mode-map)
(provide 'haskell-collapse)

Binary file not shown.

View File

@@ -0,0 +1,957 @@
;;; haskell-commands.el --- Commands that can be run on the process -*- lexical-binding: t -*-
;;; Commentary:
;;; This module provides varoius `haskell-mode' and `haskell-interactive-mode'
;;; specific commands such as show type signature, show info, haskell process
;;; commands and etc.
;; Copyright © 2014 Chris Done. All rights reserved.
;; 2016 Arthur Fayzrakhmanov
;; 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 this program. If not, see <http://www.gnu.org/licenses/>.
;;; Code:
(require 'cl-lib)
(require 'etags)
(require 'haskell-mode)
(require 'haskell-compat)
(require 'haskell-process)
(require 'haskell-font-lock)
(require 'haskell-interactive-mode)
(require 'haskell-session)
(require 'haskell-string)
(require 'haskell-presentation-mode)
(require 'haskell-utils)
(require 'highlight-uses-mode)
(require 'haskell-cabal)
(defcustom haskell-mode-stylish-haskell-path "stylish-haskell"
"Path to `stylish-haskell' executable."
:group 'haskell
:type 'string)
(defcustom haskell-interactive-set-+c
t
"Issue ':set +c' in interactive session to support type introspection."
:group 'haskell-interactive
:type 'boolean)
;;;###autoload
(defun haskell-process-restart ()
"Restart the inferior Haskell process."
(interactive)
(haskell-process-reset (haskell-interactive-process))
(haskell-process-set (haskell-interactive-process) 'command-queue nil)
(haskell-process-start (haskell-interactive-session)))
(defun haskell-process-start (session)
"Start the inferior Haskell process with a given SESSION.
You can create new session using function `haskell-session-make'."
(let ((existing-process (get-process (haskell-session-name (haskell-interactive-session)))))
(when (processp existing-process)
(haskell-interactive-mode-echo session "Restarting process ...")
(haskell-process-set (haskell-session-process session) 'is-restarting t)
(delete-process existing-process)))
(let ((process (or (haskell-session-process session)
(haskell-process-make (haskell-session-name session))))
(old-queue (haskell-process-get (haskell-session-process session)
'command-queue)))
(haskell-session-set-process session process)
(haskell-process-set-session process session)
(haskell-process-set-cmd process nil)
(haskell-process-set (haskell-session-process session) 'is-restarting nil)
(let ((default-directory (haskell-session-cabal-dir session))
(log-and-command (haskell-process-compute-process-log-and-command session (haskell-process-type))))
(haskell-session-prompt-set-current-dir session (not haskell-process-load-or-reload-prompt))
(haskell-process-set-process
process
(progn
(haskell-process-log (propertize (format "%S" log-and-command)))
(apply #'start-process (cdr log-and-command)))))
(progn (set-process-sentinel (haskell-process-process process) 'haskell-process-sentinel)
(set-process-filter (haskell-process-process process) 'haskell-process-filter))
(haskell-process-send-startup process)
(unless (or (eq 'cabal-repl (haskell-process-type))
(eq 'cabal-new-repl (haskell-process-type))
(eq 'stack-ghci (haskell-process-type))) ;; Both "cabal repl" and "stack ghci" set the proper CWD.
(haskell-process-change-dir session
process
(haskell-session-current-dir session)))
(haskell-process-set process 'command-queue
(append (haskell-process-get (haskell-session-process session)
'command-queue)
old-queue))
process))
(defun haskell-process-send-startup (process)
"Send the necessary start messages to haskell PROCESS."
(haskell-process-queue-command
process
(make-haskell-command
:state process
:go (lambda (process)
;; We must set the prompt last, so that this command as a
;; whole produces only one prompt marker as a response.
(haskell-process-send-string process
(mapconcat #'identity
(append '("Prelude.putStrLn \"\""
":set -v1")
(when haskell-interactive-set-+c
'(":set +c"))) ; :type-at in GHC 8+
"\n"))
(haskell-process-send-string process ":set prompt \"\\4\"")
(haskell-process-send-string process (format ":set prompt2 \"%s\""
haskell-interactive-prompt2)))
:live (lambda (process buffer)
(when (haskell-process-consume
process
"^\*\*\* WARNING: \\(.+\\) is writable by someone else, IGNORING!$")
(let ((path (match-string 1 buffer)))
(haskell-session-modify
(haskell-process-session process)
'ignored-files
(lambda (files)
(cl-remove-duplicates (cons path files) :test 'string=)))
(haskell-interactive-mode-compile-warning
(haskell-process-session process)
(format "GHCi is ignoring: %s (run M-x haskell-process-unignore)"
path)))))
:complete (lambda (process _)
(haskell-interactive-mode-echo
(haskell-process-session process)
(concat (nth (random (length haskell-process-greetings))
haskell-process-greetings)
(when haskell-process-show-debug-tips
"
If I break, you can:
1. Restart: M-x haskell-process-restart
2. Configure logging: C-h v haskell-process-log (useful for debugging)
3. General config: M-x customize-mode
4. Hide these tips: C-h v haskell-process-show-debug-tips")))
(with-current-buffer (haskell-interactive-buffer)
(goto-char haskell-interactive-mode-prompt-start))))))
(defun haskell-commands-process ()
"Get the Haskell session, throws an error if not available."
(or (haskell-session-process (haskell-session-maybe))
(error "No Haskell session/process associated with this
buffer. Maybe run M-x haskell-session-change?")))
;;;###autoload
(defun haskell-process-clear ()
"Clear the current process."
(interactive)
(haskell-process-reset (haskell-commands-process))
(haskell-process-set (haskell-commands-process) 'command-queue nil))
;;;###autoload
(defun haskell-process-interrupt ()
"Interrupt the process (SIGINT)."
(interactive)
(interrupt-process (haskell-process-process (haskell-commands-process))))
(defun haskell-process-reload-with-fbytecode (process module-buffer)
"Query a PROCESS to reload MODULE-BUFFER with -fbyte-code set.
Restores -fobject-code after reload finished.
MODULE-BUFFER is the actual Emacs buffer of the module being loaded."
(haskell-process-queue-without-filters process ":set -fbyte-code")
;; We prefix the module's filename with a "*", which asks ghci to
;; ignore any existing object file and interpret the module.
;; Dependencies will still use their object files as usual.
(haskell-process-queue-without-filters
process
(format ":load \"*%s\""
(replace-regexp-in-string
"\""
"\\\\\""
(buffer-file-name module-buffer))))
(haskell-process-queue-without-filters process ":set -fobject-code"))
(defvar url-http-response-status)
(defvar url-http-end-of-headers)
(defvar haskell-cabal-targets-history nil
"History list for session targets.")
(defun haskell-process-hayoo-ident (ident)
"Hayoo for IDENT, return a list of modules"
;; We need a real/simulated closure, because otherwise these
;; variables will be unbound when the url-retrieve callback is
;; called.
;; TODO: Remove when this code is converted to lexical bindings by
;; default (Emacs 24.1+)
(let ((url (format haskell-process-hayoo-query-url (url-hexify-string ident))))
(with-current-buffer (url-retrieve-synchronously url)
(if (= 200 url-http-response-status)
(progn
(goto-char url-http-end-of-headers)
(let* ((res (json-read))
(results (assoc-default 'result res)))
;; TODO: gather packages as well, and when we choose a
;; given import, check that we have the package in the
;; cabal file as well.
(cl-mapcan (lambda (r)
;; append converts from vector -> list
(append (assoc-default 'resultModules r) nil))
results)))
(warn "HTTP error %s fetching %s" url-http-response-status url)))))
(defun haskell-process-hoogle-ident (ident)
"Hoogle for IDENT, return a list of modules."
(with-temp-buffer
(let ((hoogle-error (call-process "hoogle" nil t nil "search" "--exact" ident)))
(goto-char (point-min))
(unless (or (/= 0 hoogle-error)
(looking-at "^No results found")
(looking-at "^package "))
(while (re-search-forward "^\\([^ ]+\\).*$" nil t)
(replace-match "\\1" nil nil))
(cl-remove-if (lambda (a) (string= "" a))
(split-string (buffer-string)
"\n"))))))
(defun haskell-process-haskell-docs-ident (ident)
"Search with haskell-docs for IDENT, return a list of modules."
(cl-remove-if-not
(lambda (a) (string-match "^[[:upper:]][[:alnum:]_'.]+$" a))
(split-string
(with-output-to-string
(with-current-buffer
standard-output
(call-process "haskell-docs"
nil ; no infile
t ; output to current buffer (that is string)
nil ; do not redisplay
"--modules" ident)))
"\n")))
(defun haskell-process-import-modules (process modules)
"Query PROCESS `:m +' command to import MODULES."
(when haskell-process-auto-import-loaded-modules
(haskell-process-queue-command
process
(make-haskell-command
:state (cons process modules)
:go (lambda (state)
(haskell-process-send-string
(car state)
(format ":m + %s" (mapconcat 'identity (cdr state) " "))))))))
;;;###autoload
(defun haskell-describe (ident)
"Describe the given identifier IDENT."
(interactive (list (read-from-minibuffer "Describe identifier: "
(haskell-ident-at-point))))
(let ((results (read (shell-command-to-string
(concat "haskell-docs --sexp "
ident)))))
(help-setup-xref (list #'haskell-describe ident)
(called-interactively-p 'interactive))
(save-excursion
(with-help-window (help-buffer)
(with-current-buffer (help-buffer)
(if results
(cl-loop for result in results
do (insert (propertize ident 'font-lock-face
'((:inherit font-lock-type-face
:underline t)))
" is defined in "
(let ((module (cadr (assoc 'module result))))
(if module
(concat module " ")
""))
(cadr (assoc 'package result))
"\n\n")
do (let ((type (cadr (assoc 'type result))))
(when type
(insert (haskell-fontify-as-mode type 'haskell-mode)
"\n")))
do (let ((args (cadr (assoc 'type results))))
(cl-loop for arg in args
do (insert arg "\n"))
(insert "\n"))
do (insert (cadr (assoc 'documentation result)))
do (insert "\n\n"))
(insert "No results for " ident)))))))
;;;###autoload
(defun haskell-rgrep (&optional prompt)
"Grep the effective project for the symbol at point.
Very useful for codebase navigation.
Prompts for an arbitrary regexp given a prefix arg PROMPT."
(interactive "P")
(let ((sym (if prompt
(read-from-minibuffer "Look for: ")
(haskell-ident-at-point))))
(rgrep sym
"*.hs *.lhs *.hsc *.chs *.hs-boot *.lhs-boot"
(haskell-session-current-dir (haskell-interactive-session)))))
;;;###autoload
(defun haskell-process-do-info (&optional prompt-value)
"Print info on the identifier at point.
If PROMPT-VALUE is non-nil, request identifier via mini-buffer."
(interactive "P")
(let ((at-point (haskell-ident-at-point)))
(when (or prompt-value at-point)
(let* ((ident (replace-regexp-in-string
"^!\\([A-Z_a-z]\\)"
"\\1"
(if prompt-value
(read-from-minibuffer "Info: " at-point)
at-point)))
(modname (unless prompt-value
(haskell-utils-parse-import-statement-at-point)))
(command (cond
(modname
(format ":browse! %s" modname))
((string= ident "") ; For the minibuffer input case
nil)
(t (format (if (string-match "^[a-zA-Z_]" ident)
":info %s"
":info (%s)")
(or ident
at-point))))))
(when command
(haskell-process-show-repl-response command))))))
;;;###autoload
(defun haskell-process-do-type (&optional insert-value)
"Print the type of the given expression.
Given INSERT-VALUE prefix indicates that result type signature
should be inserted."
(interactive "P")
(if insert-value
(haskell-process-insert-type)
(let* ((expr
(if (use-region-p)
(buffer-substring-no-properties (region-beginning) (region-end))
(haskell-ident-at-point)))
(expr-okay (and expr
(not (string-match-p "\\`[[:space:]]*\\'" expr))
(not (string-match-p "\n" expr)))))
;; No newlines in expressions, and surround with parens if it
;; might be a slice expression
(when expr-okay
(haskell-process-show-repl-response
(format
(if (or (string-match-p "\\`(" expr)
(string-match-p "\\`[_[:alpha:]]" expr))
":type %s"
":type (%s)")
expr))))))
;;;###autoload
(defun haskell-mode-jump-to-def-or-tag (&optional _next-p)
;; FIXME NEXT-P arg is not used
"Jump to the definition.
Jump to definition of identifier at point by consulting GHCi, or
tag table as fallback.
Remember: If GHCi is busy doing something, this will delay, but
it will always be accurate, in contrast to tags, which always
work but are not always accurate.
If the definition or tag is found, the location from which you jumped
will be pushed onto `xref--marker-ring', so you can return to that
position with `xref-pop-marker-stack'."
(interactive "P")
(if (haskell-session-maybe)
(let ((initial-loc (point-marker))
(loc (haskell-mode-find-def (haskell-ident-at-point))))
(haskell-mode-handle-generic-loc loc)
(unless (equal initial-loc (point-marker))
(xref-push-marker-stack initial-loc)))
(call-interactively 'haskell-mode-tag-find)))
;;;###autoload
(defun haskell-mode-goto-loc ()
"Go to the location of the thing at point.
Requires the :loc-at command from GHCi."
(interactive)
(let ((loc (haskell-mode-loc-at)))
(when loc
(haskell-mode-goto-span loc))))
(defun haskell-mode-goto-span (span)
"Jump to the SPAN, whatever file and line and column it needs to get there."
(xref-push-marker-stack)
(find-file (expand-file-name (plist-get span :path)
(haskell-session-cabal-dir (haskell-interactive-session))))
(goto-char (point-min))
(forward-line (1- (plist-get span :start-line)))
(forward-char (plist-get span :start-col)))
(defun haskell-process-insert-type ()
"Get the identifier at the point and insert its type.
Use GHCi's :type if it's possible."
(let ((ident (haskell-ident-at-point)))
(when ident
(let ((process (haskell-interactive-process))
(query (format (if (string-match "^[_[:lower:][:upper:]]" ident)
":type %s"
":type (%s)")
ident)))
(haskell-process-queue-command
process
(make-haskell-command
:state (list process query (current-buffer))
:go (lambda (state)
(haskell-process-send-string (nth 0 state)
(nth 1 state)))
:complete (lambda (state response)
(cond
;; TODO: Generalize this into a function.
((or (string-match "^Top level" response)
(string-match "^<interactive>" response))
(message "%s" response))
(t
(with-current-buffer (nth 2 state)
(goto-char (line-beginning-position))
(insert (format "%s\n" (replace-regexp-in-string "\n$" "" response)))))))))))))
(defun haskell-mode-find-def (ident)
;; TODO Check if it possible to exploit `haskell-process-do-info'
"Find definition location of identifier IDENT.
Uses the GHCi process to find the location. Returns nil if it
can't find the identifier or the identifier isn't a string.
Returns:
(library <package> <module>)
(file <path> <line> <col>)
(module <name>)
nil"
(when (stringp ident)
(let ((reply (haskell-process-queue-sync-request
(haskell-interactive-process)
(format (if (string-match "^[a-zA-Z_]" ident)
":info %s"
":info (%s)")
ident))))
(let ((match (string-match "-- Defined \\(at\\|in\\) \\(.+\\)$" reply)))
(when match
(let ((defined (match-string 2 reply)))
(let ((match (string-match "\\(.+?\\):\\([0-9]+\\):\\([0-9]+\\)$" defined)))
(cond
(match
(list 'file
(expand-file-name (match-string 1 defined)
(haskell-session-current-dir (haskell-interactive-session)))
(string-to-number (match-string 2 defined))
(string-to-number (match-string 3 defined))))
(t
(let ((match (string-match "`\\(.+?\\):\\(.+?\\)'$" defined)))
(if match
(list 'library
(match-string 1 defined)
(match-string 2 defined))
(let ((match (string-match "`\\(.+?\\)'$" defined)))
(if match
(list 'module
(match-string 1 defined)))))))))))))))
;;;###autoload
(defun haskell-mode-jump-to-def (ident)
"Jump to definition of identifier IDENT at point."
(interactive
(list
(haskell-string-drop-qualifier
(haskell-ident-at-point))))
(let ((loc (haskell-mode-find-def ident)))
(when loc
(haskell-mode-handle-generic-loc loc))))
(defun haskell-mode-handle-generic-loc (loc)
"Either jump to or echo a generic location LOC.
Either a file or a library."
(cl-case (car loc)
(file (progn
(find-file (elt loc 1))
(goto-char (point-min))
(forward-line (1- (elt loc 2)))
(goto-char (+ (line-beginning-position)
(1- (elt loc 3))))))
(library (message "Defined in `%s' (%s)."
(elt loc 2)
(elt loc 1)))
(module (message "Defined in `%s'."
(elt loc 1)))))
(defun haskell-mode-loc-at ()
"Get the location at point.
Requires the :loc-at command from GHCi."
(let ((pos (or (when (region-active-p)
(cons (region-beginning)
(region-end)))
(haskell-spanable-pos-at-point)
(cons (point)
(point)))))
(when pos
(let ((reply (haskell-process-queue-sync-request
(haskell-interactive-process)
(save-excursion
(format ":loc-at %s %d %d %d %d %s"
(buffer-file-name)
(progn (goto-char (car pos))
(line-number-at-pos))
(1+ (current-column)) ;; GHC uses 1-based columns.
(progn (goto-char (cdr pos))
(line-number-at-pos))
(1+ (current-column)) ;; GHC uses 1-based columns.
(buffer-substring-no-properties (car pos)
(cdr pos)))))))
(if reply
(if (string-match "\\(.*?\\):(\\([0-9]+\\),\\([0-9]+\\))-(\\([0-9]+\\),\\([0-9]+\\))"
reply)
(list :path (match-string 1 reply)
:start-line (string-to-number (match-string 2 reply))
;; ;; GHC uses 1-based columns.
:start-col (1- (string-to-number (match-string 3 reply)))
:end-line (string-to-number (match-string 4 reply))
;; GHC uses 1-based columns.
:end-col (1- (string-to-number (match-string 5 reply))))
(error (propertize reply 'face 'compilation-error)))
(error (propertize "No reply. Is :loc-at supported?"
'face 'compilation-error)))))))
;;;###autoload
(defun haskell-process-cd (&optional _not-interactive)
;; FIXME optional arg is not used
"Change directory."
(interactive)
(let* ((session (haskell-interactive-session))
(dir (haskell-session-prompt-set-current-dir session)))
(haskell-process-log
(propertize (format "Changing directory to %s ...\n" dir)
'face font-lock-comment-face))
(haskell-process-change-dir session
(haskell-interactive-process)
dir)))
(defun haskell-session-buffer-default-dir (session &optional buffer)
"Try to deduce a sensible default directory for SESSION and BUFFER,
of which the latter defaults to the current buffer."
(or (haskell-session-get session 'current-dir)
(haskell-session-get session 'cabal-dir)
(if (buffer-file-name buffer)
(file-name-directory (buffer-file-name buffer))
"~/")))
(defun haskell-session-prompt-set-current-dir (session &optional use-default)
"Prompt for the current directory.
Return current working directory for SESSION."
(let ((default (haskell-session-buffer-default-dir session)))
(haskell-session-set-current-dir
session
(if use-default
default
(haskell-utils-read-directory-name "Set current directory: " default))))
(haskell-session-get session 'current-dir))
(defun haskell-process-change-dir (session process dir)
"Change SESSION's current directory.
Query PROCESS to `:cd` to directory DIR."
(haskell-process-queue-command
process
(make-haskell-command
:state (list session process dir)
:go
(lambda (state)
(haskell-process-send-string
(cadr state) (format ":cd %s" (cl-caddr state))))
:complete
(lambda (state _)
(haskell-session-set-current-dir (car state) (cl-caddr state))
(haskell-interactive-mode-echo (car state)
(format "Changed directory: %s"
(cl-caddr state)))))))
;;;###autoload
(defun haskell-process-cabal-macros ()
"Send the cabal macros string."
(interactive)
(haskell-process-queue-without-filters (haskell-interactive-process)
":set -optP-include -optPdist/build/autogen/cabal_macros.h"))
(defun haskell-process-do-try-info (sym)
"Get info of SYM and echo in the minibuffer."
(let ((process (haskell-interactive-process)))
(haskell-process-queue-command
process
(make-haskell-command
:state (cons process sym)
:go (lambda (state)
(haskell-process-send-string
(car state)
(if (string-match "^[A-Za-z_]" (cdr state))
(format ":info %s" (cdr state))
(format ":info (%s)" (cdr state)))))
:complete (lambda (_state response)
(unless (or (string-match "^Top level" response)
(string-match "^<interactive>" response))
(haskell-mode-message-line response)))))))
(defun haskell-process-do-try-type (sym)
"Get type of SYM and echo in the minibuffer."
(let ((process (haskell-interactive-process)))
(haskell-process-queue-command
process
(make-haskell-command
:state (cons process sym)
:go (lambda (state)
(haskell-process-send-string
(car state)
(if (string-match "^[A-Za-z_]" (cdr state))
(format ":type %s" (cdr state))
(format ":type (%s)" (cdr state)))))
:complete (lambda (_state response)
(unless (or (string-match "^Top level" response)
(string-match "^<interactive>" response))
(haskell-mode-message-line response)))))))
;;;###autoload
(defun haskell-mode-show-type-at (&optional insert-value)
"Show type of the thing at point or within active region asynchronously.
This function requires GHCi 8+ or GHCi-ng.
\\<haskell-interactive-mode-map>
To make this function works sometimes you need to load the file in REPL
first using command `haskell-process-load-file' bound to
\\[haskell-process-load-file].
Optional argument INSERT-VALUE indicates that
recieved type signature should be inserted (but only if nothing
happened since function invocation)."
(interactive "P")
(let* ((pos (haskell-command-capture-expr-bounds))
(req (haskell-utils-compose-type-at-command pos))
(process (haskell-interactive-process))
(buf (current-buffer))
(pos-reg (cons pos (region-active-p))))
(haskell-process-queue-command
process
(make-haskell-command
:state (list process req buf insert-value pos-reg)
:go
(lambda (state)
(let* ((prc (car state))
(req (nth 1 state)))
(haskell-utils-async-watch-changes)
(haskell-process-send-string prc req)))
:complete
(lambda (state response)
(let* ((init-buffer (nth 2 state))
(insert-value (nth 3 state))
(pos-reg (nth 4 state))
(wrap (cdr pos-reg))
(min-pos (caar pos-reg))
(max-pos (cdar pos-reg))
(sig (haskell-utils-reduce-string response))
(res-type (haskell-utils-repl-response-error-status sig)))
(cl-case res-type
;; neither popup presentation buffer
;; nor insert response in error case
('unknown-command
(message "This command requires GHCi 8+ or GHCi-ng. Please read command description for details."))
('option-missing
(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."))
('interactive-error (message "Wrong REPL response: %s" sig))
(otherwise
(if insert-value
;; Only insert type signature and do not present it
(if (= (length haskell-utils-async-post-command-flag) 1)
(if wrap
;; Handle region case
(progn
(deactivate-mark)
(save-excursion
(delete-region min-pos max-pos)
(goto-char min-pos)
(insert (concat "(" sig ")"))))
;; Non-region cases
(haskell-command-insert-type-signature sig))
;; Some commands registered, prevent insertion
(message "Type signature insertion was prevented. These commands were registered: %s"
(cdr (reverse haskell-utils-async-post-command-flag))))
;; Present the result only when response is valid and not asked
;; to insert result
(haskell-command-echo-or-present response)))
(haskell-utils-async-stop-watching-changes init-buffer))))))))
(make-obsolete 'haskell-process-generate-tags
'haskell-mode-generate-tags
"2016-03-14")
(defun haskell-process-generate-tags (&optional and-then-find-this-tag)
"Regenerate the TAGS table.
If optional AND-THEN-FIND-THIS-TAG argument is present it is used with
function `xref-find-definitions' after new table was generated."
(interactive)
(let ((process (haskell-interactive-process)))
(haskell-process-queue-command
process
(make-haskell-command
:state (cons process and-then-find-this-tag)
:go
(lambda (state)
(let* ((process (car state))
(cabal-dir (haskell-session-cabal-dir
(haskell-process-session process)))
(command (haskell-cabal--compose-hasktags-command cabal-dir)))
(haskell-process-send-string process command)))
:complete (lambda (state _response)
(when (cdr state)
(let ((tags-file-name
(haskell-session-tags-filename
(haskell-process-session (car state)))))
(xref-find-definitions (cdr state))))
(haskell-mode-message-line "Tags generated."))))))
(defun haskell-process-add-cabal-autogen ()
"Add cabal's autogen dir to the GHCi search path.
Add <cabal-project-dir>/dist/build/autogen/ to GHCi seatch path.
This allows modules such as 'Path_...', generated by cabal, to be
loaded by GHCi."
(unless (or (eq 'cabal-repl (haskell-process-type))
(eq 'cabal-new-repl (haskell-process-type))) ;; redundant with "cabal repl"
(let*
((session (haskell-interactive-session))
(cabal-dir (haskell-session-cabal-dir session))
(ghci-gen-dir (format "%sdist/build/autogen/" cabal-dir)))
(haskell-process-queue-without-filters
(haskell-interactive-process)
(format ":set -i%s" ghci-gen-dir)))))
;;;###autoload
(defun haskell-process-unignore ()
"Unignore any ignored files.
Do not ignore files that were specified as being ignored by the
inferior GHCi process."
(interactive)
(let ((session (haskell-interactive-session))
(changed nil))
(if (null (haskell-session-get session 'ignored-files))
(message "Nothing to unignore!")
(cl-loop for file in (haskell-session-get session 'ignored-files)
do
(haskell-mode-toggle-interactive-prompt-state)
(unwind-protect
(progn
(cl-case
(read-event
(propertize
(format "Set permissions? %s (y, n, v: stop and view file)"
file)
'face
'minibuffer-prompt))
(?y
(haskell-process-unignore-file session file)
(setq changed t))
(?v
(find-file file)
(cl-return)))
(when (and changed
(y-or-n-p "Restart GHCi process now? "))
(haskell-process-restart)))
;; unwind
(haskell-mode-toggle-interactive-prompt-state t))))))
;;;###autoload
(defun haskell-session-change-target (target)
"Set the build TARGET for cabal REPL."
(interactive
(list
(completing-read "New build target: "
(haskell-cabal-enum-targets (haskell-process-type))
nil
nil
nil
'haskell-cabal-targets-history)))
(let* ((session haskell-session)
(old-target (haskell-session-get session 'target)))
(when session
(haskell-session-set-target session target)
(when (not (string= old-target target))
(haskell-mode-toggle-interactive-prompt-state)
(unwind-protect
(when (y-or-n-p "Target changed, restart haskell process?")
(haskell-process-start session)))
(haskell-mode-toggle-interactive-prompt-state t)))))
;;;###autoload
(defun haskell-mode-stylish-buffer ()
"Apply stylish-haskell to the current buffer.
Use `haskell-mode-stylish-haskell-path' to know where to find
stylish-haskell executable. This function tries to preserve
cursor position and markers by using
`haskell-mode-buffer-apply-command'."
(interactive)
(haskell-mode-buffer-apply-command haskell-mode-stylish-haskell-path))
(defun haskell-mode-buffer-apply-command (cmd)
"Execute shell command CMD with current buffer as input and output.
Use buffer as input and replace the whole buffer with the
output. If CMD fails the buffer remains unchanged."
(set-buffer-modified-p t)
(let* ((out-file (make-temp-file "stylish-output"))
(err-file (make-temp-file "stylish-error")))
(unwind-protect
(let* ((_errcode
(call-process-region (point-min) (point-max) cmd nil
`((:file ,out-file) ,err-file)
nil))
(err-file-empty-p
(equal 0 (nth 7 (file-attributes err-file))))
(out-file-empty-p
(equal 0 (nth 7 (file-attributes out-file)))))
(if err-file-empty-p
(if out-file-empty-p
(message "Error: %s produced no output and no error information, leaving buffer alone" cmd)
;; Command successful, insert file with replacement to preserve
;; markers.
(insert-file-contents out-file nil nil nil t))
(progn
;; non-null stderr, command must have failed
(message "Error: %s ended with errors, leaving buffer alone" cmd)
(with-temp-buffer
(insert-file-contents err-file)
;; use (warning-minimum-level :debug) to see this
(display-warning cmd
(buffer-substring-no-properties (point-min) (point-max))
:debug)))))
(ignore-errors
(delete-file err-file))
(ignore-errors
(delete-file out-file)))))
;;;###autoload
(defun haskell-mode-find-uses ()
"Find use cases of the identifier at point and highlight them all."
(interactive)
(let ((spans (haskell-mode-uses-at)))
(unless (null spans)
(highlight-uses-mode 1)
(cl-loop for span in spans
do (haskell-mode-make-use-highlight span)))))
(defun haskell-mode-make-use-highlight (span)
"Make a highlight overlay at the given SPAN."
(save-window-excursion
(save-excursion
(haskell-mode-goto-span span)
(save-excursion
(highlight-uses-mode-highlight
(progn
(goto-char (point-min))
(forward-line (1- (plist-get span :start-line)))
(forward-char (plist-get span :start-col))
(point))
(progn
(goto-char (point-min))
(forward-line (1- (plist-get span :end-line)))
(forward-char (plist-get span :end-col))
(point)))))))
(defun haskell-mode-uses-at ()
"Get the locations of use cases for the ident at point.
Requires the :uses command from GHCi."
(let ((pos (or (when (region-active-p)
(cons (region-beginning)
(region-end)))
(haskell-ident-pos-at-point)
(cons (point)
(point)))))
(when pos
(let ((reply (haskell-process-queue-sync-request
(haskell-interactive-process)
(save-excursion
(format ":uses %s %d %d %d %d %s"
(buffer-file-name)
(progn (goto-char (car pos))
(line-number-at-pos))
(1+ (current-column)) ;; GHC uses 1-based columns.
(progn (goto-char (cdr pos))
(line-number-at-pos))
(1+ (current-column)) ;; GHC uses 1-based columns.
(buffer-substring-no-properties (car pos)
(cdr pos)))))))
(if reply
(let ((lines (split-string reply "\n" t)))
(cl-remove-if
#'null
(mapcar (lambda (line)
(if (string-match "\\(.*?\\):(\\([0-9]+\\),\\([0-9]+\\))-(\\([0-9]+\\),\\([0-9]+\\))"
line)
(list :path (match-string 1 line)
:start-line (string-to-number (match-string 2 line))
;; ;; GHC uses 1-based columns.
:start-col (1- (string-to-number (match-string 3 line)))
:end-line (string-to-number (match-string 4 line))
;; GHC uses 1-based columns.
:end-col (1- (string-to-number (match-string 5 line))))
(error (propertize line 'face 'compilation-error))))
lines)))
(error (propertize "No reply. Is :uses supported?"
'face 'compilation-error)))))))
(defun haskell-command-echo-or-present (msg)
"Present message in some manner depending on configuration.
If variable `haskell-process-use-presentation-mode' is NIL it will output
modified message MSG to echo area."
(if haskell-process-use-presentation-mode
(let ((session (haskell-process-session (haskell-interactive-process))))
(haskell-presentation-present session msg))
(let ((m (haskell-utils-reduce-string msg)))
(message "%s" m))))
(defun haskell-command-capture-expr-bounds ()
"Capture position bounds of expression at point.
If there is an active region then it returns region
bounds. Otherwise it uses `haskell-spanable-pos-at-point` to
capture identifier bounds. If latter function returns NIL this function
will return cons cell where min and max positions both are equal
to point."
(or (when (region-active-p)
(cons (region-beginning)
(region-end)))
(haskell-spanable-pos-at-point)
(cons (point) (point))))
(defun haskell-command-insert-type-signature (signature)
"Insert type signature.
In case of active region is present, wrap it by parentheses and
append SIGNATURE to original expression. Otherwise tries to
carefully insert SIGNATURE above identifier at point. Removes
newlines and extra whitespace in signature before insertion."
(let* ((ident-pos (or (haskell-ident-pos-at-point)
(cons (point) (point))))
(min-pos (car ident-pos))
(sig (haskell-utils-reduce-string signature)))
(save-excursion
(goto-char min-pos)
(let ((col (current-column)))
(insert sig "\n")
(indent-to col)))))
(provide 'haskell-commands)
;;; haskell-commands.el ends here

Binary file not shown.

View File

@@ -0,0 +1,65 @@
;;; haskell-compat.el --- legacy/compatibility backports for haskell-mode -*- lexical-binding: t -*-
;;
;; Filename: haskell-compat.el
;; Description: legacy/compatibility backports for haskell-mode
;; This file is not part of GNU Emacs.
;; 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 this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;;; Code:
(require 'etags)
(require 'ring)
(require 'outline)
(require 'xref nil t)
(eval-when-compile
(setq byte-compile-warnings '(not cl-functions obsolete)))
;; Cross-referencing commands have been replaced since Emacs 25.1.
;; These aliases are required to provide backward compatibility.
(unless (fboundp 'xref-push-marker-stack)
(defalias 'xref-pop-marker-stack 'pop-tag-mark)
(defun xref-push-marker-stack (&optional m)
"Add point M (defaults to `point-marker') to the marker stack."
(ring-insert find-tag-marker-ring (or m (point-marker)))))
(unless (fboundp 'outline-hide-sublevels)
(defalias 'outline-hide-sublevels 'hide-sublevels))
(unless (fboundp 'outline-show-subtree)
(defalias 'outline-show-subtree 'show-subtree))
(unless (fboundp 'outline-hide-sublevels)
(defalias 'outline-hide-sublevels 'hide-sublevels))
(unless (fboundp 'outline-show-subtree)
(defalias 'outline-show-subtree 'show-subtree))
(unless (fboundp 'xref-find-definitions)
(defun xref-find-definitions (ident)
(let ((next-p (and (boundp 'xref-prompt-for-identifier)
xref-prompt-for-identifier)))
(find-tag ident next-p))))
(unless (fboundp 'font-lock-ensure)
(defalias 'font-lock-ensure 'font-lock-fontify-buffer))
(provide 'haskell-compat)
;;; haskell-compat.el ends here

Binary file not shown.

View File

@@ -0,0 +1,154 @@
;;; haskell-compile.el --- Haskell/GHC compilation sub-mode -*- lexical-binding: t -*-
;; Copyright (C) 2013 Herbert Valerio Riedel
;; Author: Herbert Valerio Riedel <hvr@gnu.org>
;; This file is not part of GNU Emacs.
;; 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 of the License, 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 this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; Simple GHC-centric compilation sub-mode; see info node
;; `(haskell-mode)compilation' for more information
;;; Code:
(require 'compile)
(require 'haskell-cabal)
;;;###autoload
(defgroup haskell-compile nil
"Settings for Haskell compilation mode"
:link '(custom-manual "(haskell-mode)compilation")
:group 'haskell)
(defcustom haskell-compile-cabal-build-command
"cd %s && cabal build --ghc-option=-ferror-spans"
"Default build command to use for `haskell-cabal-build' when a cabal file is detected.
The `%s' placeholder is replaced by the cabal package top folder."
:group 'haskell-compile
:type 'string)
(defcustom haskell-compile-cabal-build-alt-command
"cd %s && cabal clean -s && cabal build --ghc-option=-ferror-spans"
"Alternative build command to use when `haskell-cabal-build' is called with a negative prefix argument.
The `%s' placeholder is replaced by the cabal package top folder."
:group 'haskell-compile
:type 'string)
(defcustom haskell-compile-command
"ghc -Wall -ferror-spans -fforce-recomp -c %s"
"Default build command to use for `haskell-cabal-build' when no cabal file is detected.
The `%s' placeholder is replaced by the current buffer's filename."
:group 'haskell-compile
:type 'string)
(defcustom haskell-compile-ghc-filter-linker-messages
t
"Filter out unremarkable \"Loading package...\" linker messages during compilation."
:group 'haskell-compile
:type 'boolean)
(defconst haskell-compilation-error-regexp-alist
`((,(concat
"^ *\\(?1:[^\t\r\n]+?\\):"
"\\(?:"
"\\(?2:[0-9]+\\):\\(?4:[0-9]+\\)\\(?:-\\(?5:[0-9]+\\)\\)?" ;; "121:1" & "12:3-5"
"\\|"
"(\\(?2:[0-9]+\\),\\(?4:[0-9]+\\))-(\\(?3:[0-9]+\\),\\(?5:[0-9]+\\))" ;; "(289,5)-(291,36)"
"\\)"
":\\(?6:\n?[ \t]+[Ww]arning:\\)?")
1 (2 . 3) (4 . 5) (6 . nil)) ;; error/warning locus
;; multiple declarations
("^ \\(?:Declared at:\\| \\) \\(?1:[^ \t\r\n]+\\):\\(?2:[0-9]+\\):\\(?4:[0-9]+\\)$"
1 2 4 0) ;; info locus
;; this is the weakest pattern as it's subject to line wrapping et al.
(" at \\(?1:[^ \t\r\n]+\\):\\(?2:[0-9]+\\):\\(?4:[0-9]+\\)\\(?:-\\(?5:[0-9]+\\)\\)?[)]?$"
1 2 (4 . 5) 0)) ;; info locus
"Regexps used for matching GHC compile messages.
See `compilation-error-regexp-alist' for semantics.")
(defvar haskell-compilation-mode-map
(let ((map (make-sparse-keymap)))
(set-keymap-parent map compilation-mode-map))
"Keymap for `haskell-compilation-mode' buffers.
This is a child of `compilation-mode-map'.")
(defun haskell-compilation-filter-hook ()
"Local `compilation-filter-hook' for `haskell-compilation-mode'."
(when haskell-compile-ghc-filter-linker-messages
(delete-matching-lines "^ *Loading package [^ \t\r\n]+ [.]+ linking [.]+ done\\.$"
(save-excursion (goto-char compilation-filter-start)
(line-beginning-position))
(point))))
(define-compilation-mode haskell-compilation-mode "HsCompilation"
"Haskell/GHC specific `compilation-mode' derivative.
This mode provides support for GHC 7.[46]'s compile
messages. Specifically, also the `-ferror-spans` source location
format is supported, as well as info-locations within compile
messages pointing to additional source locations."
(setq-local compilation-error-regexp-alist
haskell-compilation-error-regexp-alist)
(add-hook 'compilation-filter-hook
'haskell-compilation-filter-hook nil t)
)
;;;###autoload
(defun haskell-compile (&optional edit-command)
"Compile the Haskell program including the current buffer.
Tries to locate the next cabal description in current or parent
folders via `haskell-cabal-find-dir' and if found, invoke
`haskell-compile-cabal-build-command' from the cabal package root
folder. If no cabal package could be detected,
`haskell-compile-command' is used instead.
If prefix argument EDIT-COMMAND is non-nil (and not a negative
prefix `-'), `haskell-compile' prompts for custom compile
command.
If EDIT-COMMAND contains the negative prefix argument `-',
`haskell-compile' calls the alternative command defined in
`haskell-compile-cabal-build-alt-command' if a cabal package was
detected.
`haskell-compile' uses `haskell-compilation-mode' which is
derived from `compilation-mode'. See Info
node `(haskell-mode)compilation' for more details."
(interactive "P")
(save-some-buffers (not compilation-ask-about-save)
compilation-save-buffers-predicate)
(let* ((cabdir (haskell-cabal-find-dir))
(command1 (if (eq edit-command '-)
haskell-compile-cabal-build-alt-command
haskell-compile-cabal-build-command))
(srcname (buffer-file-name))
(command (if cabdir
(format command1 cabdir)
(if (and srcname (derived-mode-p 'haskell-mode))
(format haskell-compile-command srcname)
command1))))
(when (and edit-command (not (eq edit-command '-)))
(setq command (compilation-read-command command)))
(compilation-start command 'haskell-compilation-mode)))
(provide 'haskell-compile)
;;; haskell-compile.el ends here

Binary file not shown.

View File

@@ -0,0 +1,131 @@
;;; haskell-complete-module.el --- A fast way to complete Haskell module names -*- lexical-binding: t -*-
;; Copyright (c) 2014 Chris Done. All rights reserved.
;; 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 this program. If not, see <http://www.gnu.org/licenses/>.
;;; Code:
(require 'cl-lib)
(defcustom haskell-complete-module-preferred
'()
"Override ordering of module results by specifying preferred modules."
:group 'haskell
:type '(repeat string))
(defcustom haskell-complete-module-max-display
10
"Maximum items to display in minibuffer."
:group 'haskell
:type 'number)
(defun haskell-complete-module-read (prompt candidates)
"Interactively auto-complete from a list of candidates."
(let ((stack (list))
(pattern "")
(result nil))
(delete-dups candidates)
(setq candidates
(sort candidates
(lambda (a b)
(let ((a-mem (member a haskell-complete-module-preferred))
(b-mem (member b haskell-complete-module-preferred)))
(cond
((and a-mem (not b-mem))
t)
((and b-mem (not a-mem))
nil)
(t
(string< a b)))))))
(while (not result)
(let ((key
(key-description
(vector
(read-key
(concat (propertize prompt 'face 'minibuffer-prompt)
(propertize pattern 'face 'font-lock-type-face)
"{"
(mapconcat #'identity
(let* ((i 0))
(cl-loop for candidate in candidates
while (<= i haskell-complete-module-max-display)
do (cl-incf i)
collect (cond ((> i haskell-complete-module-max-display)
"...")
((= i 1)
(propertize candidate 'face 'ido-first-match-face))
(t candidate))))
" | ")
"}"))))))
(cond
((string= key "C-g")
(keyboard-quit))
((string= key "DEL")
(unless (null stack)
(setq candidates (pop stack)))
(unless (string= "" pattern)
(setq pattern (substring pattern 0 -1))))
((string= key "RET")
(setq result (or (car candidates)
pattern)))
((string= key "<left>")
(setq candidates
(append (last candidates)
(butlast candidates))))
((string= key "<right>")
(setq candidates
(append (cdr candidates)
(list (car candidates)))))
(t
(when (string-match "[A-Za-z0-9_'.]+" key)
(push candidates stack)
(setq pattern (concat pattern key))
(setq candidates (haskell-complete-module pattern candidates)))))))
result))
(defun haskell-complete-module (pattern candidates)
"Filter the CANDIDATES using PATTERN."
(let ((case-fold-search t))
(cl-loop for candidate in candidates
when (haskell-complete-module-match pattern candidate)
collect candidate)))
(defun haskell-complete-module-match (pattern text)
"Match PATTERN against TEXT."
(string-match (haskell-complete-module-regexp pattern)
text))
(defun haskell-complete-module-regexp (pattern)
"Make a regular expression for the given module pattern. Example:
\"c.m.s\" -> \"^c[^.]*\\.m[^.]*\\.s[^.]*\"
"
(let ((components (mapcar #'haskell-complete-module-component
(split-string pattern "\\." t))))
(concat "^"
(mapconcat #'identity
components
"\\."))))
(defun haskell-complete-module-component (component)
"Make a regular expression for the given component. Example:
\"co\" -> \"c[^.]*o[^.]*\"
"
(replace-regexp-in-string "\\(.\\)" "\\1[^.]*" component))
(provide 'haskell-complete-module)

View File

@@ -0,0 +1,392 @@
;;; haskell-completions.el --- Haskell Completion package -*- lexical-binding: t -*-
;; 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:
;; 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)
;;;###autoload
(defgroup haskell-completions nil
"Settings for completions provided by `haskell-mode'"
:link '(custom-manual "(haskell-mode)Completion support")
:group 'haskell)
(defcustom haskell-completions-complete-operators
t
"Should `haskell-completions-sync-repl-completion-at-point' complete operators.
Note: GHCi prior to version 8.0.1 have bug in `:complete`
command: when completing operators it returns a list of all
imported identifiers (see Track ticket URL
`https://ghc.haskell.org/trac/ghc/ticket/10576'). This leads to
significant Emacs slowdown. To aviod slowdown you should set
this variable to `nil'."
:group 'haskell-completions
:type 'boolean)
(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'.")
(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.
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 presence 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 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, 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)
(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--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
(when (not (eql typ 'haskell-completions-general-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
(append (when (bound-and-true-p haskell-tags-on-save)
tags-completion-table)
haskell-completions--keywords)))))
(list beg end candidates)))))
;;;###autoload
(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)))
(when 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.
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
(when (and (not (eql typ 'haskell-completions-general-prefix))
(or haskell-completions-complete-operators
(not (save-excursion
(goto-char (1- end))
(haskell-mode--looking-at-varsym)))))
;; 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)
;; 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)))
;; few possible extra checks would be:
;; (haskell-process-get 'is-restarting)
;; (haskell-process-get 'evaluating)
)
;; 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.
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

Binary file not shown.

View File

@@ -0,0 +1,467 @@
;;; haskell-customize.el --- Customization settings -*- lexical-binding: t -*-
;; Copyright (c) 2014 Chris Done. All rights reserved.
;; 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 this program. If not, see <http://www.gnu.org/licenses/>.
;;; Code:
(require 'cl-lib)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Customization variables
(defcustom haskell-process-load-or-reload-prompt nil
"Nil means there will be no prompts on starting REPL. Defaults will be accepted."
:type 'boolean
:group 'haskell-interactive)
;;;###autoload
(defgroup haskell nil
"Major mode for editing Haskell programs."
:link '(custom-manual "(haskell-mode)")
:group 'languages
:prefix "haskell-")
(defvar haskell-mode-pkg-base-dir (file-name-directory load-file-name)
"Package base directory of installed `haskell-mode'.
Used for locating additional package data files.")
(defcustom haskell-completing-read-function 'ido-completing-read
"Default function to use for completion."
:group 'haskell
:type '(choice
(function-item :tag "ido" :value ido-completing-read)
(function-item :tag "helm" :value helm--completing-read-default)
(function-item :tag "completing-read" :value completing-read)
(function :tag "Custom function")))
(defcustom haskell-process-type
'auto
"The inferior Haskell process type to use.
When set to 'auto (the default), the directory contents and
available programs will be used to make a best guess at the
process type:
If the project directory or one of its parents contains a
\"cabal.sandbox.config\" file, then cabal-repl will be used.
If there's a \"stack.yaml\" file and the \"stack\" executable can
be located, then stack-ghci will be used.
Otherwise if there's a *.cabal file, cabal-repl will be used.
If none of the above apply, ghci will be used."
:type '(choice (const auto)
(const ghci)
(const cabal-repl)
(const stack-ghci)
(const cabal-new-repl))
:group 'haskell-interactive)
(defcustom haskell-process-wrapper-function
#'identity
"Wrap or transform haskell process commands using this function.
Can be set to a custom function which takes a list of arguments
and returns a possibly-modified list.
The following example function arranges for all haskell process
commands to be started in the current nix-shell environment:
(lambda (argv) (append (list \"nix-shell\" \"-I\" \".\" \"--command\" )
(list (mapconcat 'identity argv \" \"))))
See Info Node `(emacs)Directory Variables' for a way to set this option on
a per-project basis."
:group 'haskell-interactive
:type '(choice
(function-item :tag "None" :value identity)
(function :tag "Custom function"))
:safe 'functionp)
(defcustom haskell-ask-also-kill-buffers
t
"Ask whether to kill all associated buffers when a session
process is killed."
:type 'boolean
:group 'haskell-interactive)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Configuration
(defcustom haskell-doc-prettify-types t
"Replace some parts of types with Unicode characters like \"\"
when showing type information about symbols."
:group 'haskell-doc
:type 'boolean
:safe 'booleanp)
(defvar haskell-process-ended-functions (list 'haskell-process-prompt-restart)
"Hook for when the haskell process ends.")
;;;###autoload
(defgroup haskell-interactive nil
"Settings for REPL interaction via `haskell-interactive-mode'"
:link '(custom-manual "(haskell-mode)haskell-interactive-mode")
:group 'haskell)
(defcustom haskell-process-path-ghci
"ghci"
"The path for starting ghci.
This can either be a single string or a list of strings, where the
first elements is a string and the remaining elements are arguments,
which will be prepended to `haskell-process-args-ghci'."
:group 'haskell-interactive
:type '(choice string (repeat string)))
(defcustom haskell-process-path-cabal
"cabal"
"Path to the `cabal' executable.
This can either be a single string or a list of strings, where the
first elements is a string and the remaining elements are arguments,
which will be prepended to `haskell-process-args-cabal-repl'."
:group 'haskell-interactive
:type '(choice string (repeat string)))
(defcustom haskell-process-path-stack
"stack"
"The path for starting stack.
This can either be a single string or a list of strings, where the
first elements is a string and the remaining elements are arguments,
which will be prepended to `haskell-process-args-stack-ghci'."
:group 'haskell-interactive
:type '(choice string (repeat string)))
(defcustom haskell-process-args-ghci
'("-ferror-spans")
"Any arguments for starting ghci."
:group 'haskell-interactive
:type '(repeat (string :tag "Argument")))
(defcustom haskell-process-args-cabal-repl
'("--ghc-option=-ferror-spans")
"Additional arguments for `cabal repl' invocation.
Note: The settings in `haskell-process-path-ghci' and
`haskell-process-args-ghci' are not automatically reused as `cabal repl'
currently invokes `ghc --interactive'. Use
`--with-ghc=<path-to-executable>' if you want to use a different
interactive GHC frontend; use `--ghc-option=<ghc-argument>' to
pass additional flags to `ghc'."
:group 'haskell-interactive
:type '(repeat (string :tag "Argument")))
(defcustom haskell-process-args-cabal-new-repl
'("--ghc-option=-ferror-spans")
"Additional arguments for `cabal new-repl' invocation.
Note: The settings in `haskell-process-path-ghci' and
`haskell-process-args-ghci' are not automatically reused as
`cabal new-repl' currently invokes `ghc --interactive'. Use
`--with-ghc=<path-to-executable>' if you want to use a different
interactive GHC frontend; use `--ghc-option=<ghc-argument>' to
pass additional flags to `ghc'."
:group 'haskell-interactive
:type '(repeat (string :tag "Argument")))
(defcustom haskell-process-args-stack-ghci
'("--ghci-options=-ferror-spans" "--no-build" "--no-load")
"Additional arguments for `stack ghci' invocation."
:group 'haskell-interactive
:type '(repeat (string :tag "Argument")))
(defcustom haskell-process-do-cabal-format-string
":!cd %s && %s"
"The way to run cabal comands. It takes two arguments -- the directory and the command.
See `haskell-process-do-cabal' for more details."
:group 'haskell-interactive
:type 'string)
(defcustom haskell-process-log
nil
"Enable debug logging to \"*haskell-process-log*\" buffer."
:type 'boolean
:group 'haskell-interactive)
(defcustom haskell-process-show-debug-tips
t
"Show debugging tips when starting the process."
:type 'boolean
:group 'haskell-interactive)
(defcustom haskell-process-show-overlays
t
"Show in-buffer overlays for errors/warnings.
Flycheck users might like to disable this."
:type 'boolean
:group 'haskell-interactive)
(defcustom haskell-notify-p
nil
"Notify using notifications.el (if loaded)?"
:type 'boolean
:group 'haskell-interactive)
(defcustom haskell-process-suggest-no-warn-orphans
t
"Suggest adding -fno-warn-orphans pragma to file when getting orphan warnings."
:type 'boolean
:group 'haskell-interactive)
(defcustom haskell-process-suggest-hoogle-imports
nil
"Suggest to add import statements using Hoogle as a backend."
:type 'boolean
:group 'haskell-interactive)
(defcustom haskell-process-suggest-hayoo-imports
nil
"Suggest to add import statements using Hayoo as a backend."
:type 'boolean
:group 'haskell-interactive)
(defcustom haskell-process-hayoo-query-url
"http://hayoo.fh-wedel.de/json/?query=%s"
"Query url for json hayoo results."
:type 'string
:group 'haskell-interactive)
(defcustom haskell-process-suggest-haskell-docs-imports
nil
"Suggest to add import statements using haskell-docs as a backend."
:type 'boolean
:group 'haskell-interactive)
(defcustom haskell-process-suggest-add-package
t
"Suggest to add packages to your .cabal file when Cabal says it
is a member of the hidden package, blah blah."
:type 'boolean
:group 'haskell-interactive)
(defcustom haskell-process-suggest-language-pragmas
t
"Suggest adding LANGUAGE pragmas recommended by GHC."
:type 'boolean
:group 'haskell-interactive)
(defcustom haskell-process-suggest-remove-import-lines
nil
"Suggest removing import lines as warned by GHC."
:type 'boolean
:group 'haskell-interactive)
(defcustom haskell-process-suggest-overloaded-strings
t
"Suggest adding OverloadedStrings pragma to file when getting type mismatches with [Char]."
:type 'boolean
:group 'haskell-interactive)
(defcustom haskell-process-check-cabal-config-on-load
t
"Check changes cabal config on loading Haskell files and
restart the GHCi process if changed.."
:type 'boolean
:group 'haskell-interactive)
(defcustom haskell-process-prompt-restart-on-cabal-change
t
"Ask whether to restart the GHCi process when the Cabal file
has changed?"
:type 'boolean
:group 'haskell-interactive)
(defcustom haskell-process-auto-import-loaded-modules
nil
"Auto import the modules reported by GHC to have been loaded?"
:type 'boolean
:group 'haskell-interactive)
(defcustom haskell-process-reload-with-fbytecode
nil
"When using -fobject-code, auto reload with -fbyte-code (and
then restore the -fobject-code) so that all module info and
imports become available?"
:type 'boolean
:group 'haskell-interactive)
(defcustom haskell-process-use-presentation-mode
nil
"Use presentation mode to show things like type info instead of
printing to the message area."
:type 'boolean
:group 'haskell-interactive)
(defcustom haskell-process-suggest-restart
t
"Suggest restarting the process when it has died"
:type 'boolean
:group 'haskell-interactive)
(defcustom haskell-interactive-popup-errors
t
"Popup errors in a separate buffer."
:type 'boolean
:group 'haskell-interactive)
(defcustom haskell-interactive-mode-collapse
nil
"Collapse printed results."
:type 'boolean
:group 'haskell-interactive)
(defcustom haskell-interactive-types-for-show-ambiguous
t
"Show types when there's no Show instance or there's an
ambiguous class constraint."
:type 'boolean
:group 'haskell-interactive)
(defcustom haskell-interactive-prompt "λ> "
"The prompt to use."
:type 'string
:group 'haskell-interactive)
(defcustom haskell-interactive-prompt2 (replace-regexp-in-string
"> $"
"| "
haskell-interactive-prompt)
"The multi-line prompt to use.
The default is `haskell-interactive-prompt' with the last > replaced with |."
:type 'string
:group 'haskell-interactive)
(defcustom haskell-interactive-mode-eval-mode
nil
"Use the given mode's font-locking to render some text."
:type '(choice function (const :tag "None" nil))
:group 'haskell-interactive)
(defcustom haskell-interactive-mode-hide-multi-line-errors
nil
"Hide collapsible multi-line compile messages by default."
:type 'boolean
:group 'haskell-interactive)
(defcustom haskell-interactive-mode-delete-superseded-errors
t
"Whether to delete compile messages superseded by recompile/reloads."
:type 'boolean
:group 'haskell-interactive)
(defcustom haskell-interactive-mode-include-file-name
t
"Include the file name of the module being compiled when
printing compilation messages."
:type 'boolean
:group 'haskell-interactive)
(defcustom haskell-interactive-mode-read-only
t
"Non-nil means most GHCi/haskell-interactive-mode output is read-only.
This does not include the prompt. Configure
`haskell-interactive-prompt-read-only' to change the prompt's
read-only property."
:type 'boolean
:group 'haskell-interactive)
(defcustom haskell-interactive-prompt-read-only
haskell-interactive-mode-read-only
"Non-nil means the prompt (and prompt2) is read-only."
:type 'boolean
:group 'haskell-interactive)
(defcustom haskell-import-mapping
'()
"Support a mapping from module to import lines.
E.g. '((\"Data.Map\" . \"import qualified Data.Map as M
import Data.Map (Map)
\"))
This will import
import qualified Data.Map as M
import Data.Map (Map)
when Data.Map is the candidate.
"
:type '(repeat (cons (string :tag "Module name")
(string :tag "Import lines")))
:group 'haskell-interactive)
(defcustom haskell-language-extensions
'()
"Language extensions in use. Should be in format: -XFoo,
-XNoFoo etc. The idea is that various tools written with HSE (or
any haskell-mode code that needs to be aware of syntactical
properties; such as an indentation mode) that don't know what
extensions to use can use this variable. Examples: hlint,
hindent, structured-haskell-mode, tool-de-jour, etc.
You can set this per-project with a .dir-locals.el file"
:group 'haskell
:type '(repeat 'string))
(defcustom haskell-stylish-on-save nil
"Whether to run stylish-haskell on the buffer before saving.
If this is true, `haskell-add-import' will not sort or align the
imports."
:group 'haskell
:type 'boolean)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Accessor functions
(defvar inferior-haskell-root-dir nil
"The path which is considered as project root, this is determined by the
presence of a *.cabal file or stack.yaml file or something similar.")
(defun haskell-process-type ()
"Return `haskell-process-type', or a guess if that variable is 'auto.
This function also sets the `inferior-haskell-root-dir'"
(let ((cabal-sandbox (locate-dominating-file default-directory
"cabal.sandbox.config"))
(stack (locate-dominating-file default-directory
"stack.yaml"))
(cabal (locate-dominating-file default-directory
(lambda (d)
(cl-find-if
(lambda (f)
(string-match-p ".\\.cabal\\'" f))
(directory-files d))))))
(if (eq 'auto haskell-process-type)
(cond
;; User has explicitly initialized this project with cabal
((and cabal-sandbox
(executable-find "cabal"))
(setq inferior-haskell-root-dir cabal-sandbox)
'cabal-repl)
((and stack
(executable-find "stack"))
(setq inferior-haskell-root-dir stack)
'stack-ghci)
((and cabal
(executable-find "cabal"))
(setq inferior-haskell-root-dir cabal)
'cabal-repl)
((executable-find "ghc")
(setq inferior-haskell-root-dir default-directory)
'ghci)
(t
(error "Could not find any installation of GHC.")))
haskell-process-type)))
(provide 'haskell-customize)

Binary file not shown.

View File

@@ -0,0 +1,757 @@
;;; haskell-debug.el --- Debugging mode via GHCi -*- lexical-binding: t -*-
;; Copyright © 2014 Chris Done. All rights reserved.
;; 2016 Arthur Fayzrakhmanov
;; 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 this program. If not, see <http://www.gnu.org/licenses/>.
;;; Code:
(require 'cl-lib)
(require 'haskell-session)
(require 'haskell-process)
(require 'haskell-interactive-mode)
(require 'haskell-font-lock)
(require 'haskell-utils)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Configuration
;;;###autoload
(defgroup haskell-debug nil
"Settings for debugging support."
:link '(custom-manual "(haskell-mode)haskell-debug")
:group 'haskell)
;;;###autoload
(defface haskell-debug-warning-face
'((t :inherit 'compilation-warning))
"Face for warnings."
:group 'haskell-debug)
;;;###autoload
(defface haskell-debug-trace-number-face
'((t :weight bold :background "#f5f5f5"))
"Face for numbers in backtrace."
:group 'haskell-debug)
;;;###autoload
(defface haskell-debug-newline-face
'((t :weight bold :background "#f0f0f0"))
"Face for newlines in trace steps."
:group 'haskell-debug)
;;;###autoload
(defface haskell-debug-keybinding-face
'((t :inherit 'font-lock-type-face :weight bold))
"Face for keybindings."
:group 'haskell-debug)
;;;###autoload
(defface haskell-debug-heading-face
'((t :inherit 'font-lock-keyword-face))
"Face for headings."
:group 'haskell-debug)
;;;###autoload
(defface haskell-debug-muted-face
'((t :foreground "#999"))
"Face for muteds."
:group 'haskell-debug)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Mode
(defvar haskell-debug-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "g") 'haskell-debug/refresh)
(define-key map (kbd "s") 'haskell-debug/step)
(define-key map (kbd "t") 'haskell-debug/trace)
(define-key map (kbd "d") 'haskell-debug/delete)
(define-key map (kbd "b") 'haskell-debug/break-on-function)
(define-key map (kbd "a") 'haskell-debug/abandon)
(define-key map (kbd "c") 'haskell-debug/continue)
(define-key map (kbd "p") 'haskell-debug/previous)
(define-key map (kbd "n") 'haskell-debug/next)
(define-key map (kbd "RET") 'haskell-debug/select)
map)
"Keymap for `haskell-debug-mode'.")
(define-derived-mode haskell-debug-mode
text-mode "Debug"
"Major mode for debugging Haskell via GHCi.")
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Globals
(defvar haskell-debug-history-cache nil
"Cache of the tracing history.")
(defvar haskell-debug-bindings-cache nil
"Cache of the current step's bindings.")
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Macros
(defmacro haskell-debug-with-breakpoints (&rest body)
"Breakpoints need to exist to start stepping."
`(if (haskell-debug-get-breakpoints)
,@body
(error "No breakpoints to step into!")))
(defmacro haskell-debug-with-modules (&rest body)
"Modules need to exist to do debugging stuff."
`(if (haskell-debug-get-modules)
,@body
(error "No modules loaded!")))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Interactive functions
(defun haskell-debug/select ()
"Select whatever is at point."
(interactive)
(cond
((get-text-property (point) 'break)
(let ((break (get-text-property (point) 'break)))
(haskell-debug-highlight (plist-get break :path)
(plist-get break :span))))
((get-text-property (point) 'module)
(let ((break (get-text-property (point) 'module)))
(haskell-debug-highlight (plist-get break :path))))))
(defun haskell-debug/abandon ()
"Abandon the current computation."
(interactive)
(haskell-debug-with-breakpoints
(haskell-process-queue-sync-request (haskell-debug-process) ":abandon")
(message "Computation abandoned.")
(setq haskell-debug-history-cache nil)
(setq haskell-debug-bindings-cache nil)
(haskell-debug/refresh)))
(defun haskell-debug/continue ()
"Continue the current computation."
(interactive)
(haskell-debug-with-breakpoints
(haskell-process-queue-sync-request (haskell-debug-process) ":continue")
(message "Computation continued.")
(setq haskell-debug-history-cache nil)
(setq haskell-debug-bindings-cache nil)
(haskell-debug/refresh)))
(defun haskell-debug/break-on-function ()
"Break on function IDENT."
(interactive)
(haskell-debug-with-modules
(let ((ident (read-from-minibuffer "Function: "
(haskell-ident-at-point))))
(haskell-process-queue-sync-request
(haskell-debug-process)
(concat ":break "
ident))
(message "Breaking on function: %s" ident)
(haskell-debug/refresh))))
(defun haskell-debug/start-step (expr)
"Start stepping EXPR."
(interactive (list (read-from-minibuffer "Expression to step through: ")))
(haskell-debug/step expr))
(defun haskell-debug/breakpoint-numbers ()
"List breakpoint numbers."
(interactive)
(let ((breakpoints (mapcar (lambda (breakpoint)
(number-to-string (plist-get breakpoint :number)))
(haskell-debug-get-breakpoints))))
(if (null breakpoints)
(message "No breakpoints.")
(message "Breakpoint(s): %s"
(mapconcat #'identity
breakpoints
", ")))))
(defun haskell-debug/next ()
"Go to next step to inspect bindings."
(interactive)
(haskell-debug-with-breakpoints
(haskell-debug-navigate "forward")))
(defun haskell-debug/previous ()
"Go to previous step to inspect the bindings."
(interactive)
(haskell-debug-with-breakpoints
(haskell-debug-navigate "back")))
(defun haskell-debug/refresh ()
"Refresh the debugger buffer."
(interactive)
(with-current-buffer (haskell-debug-buffer-name (haskell-debug-session))
(cd (haskell-session-current-dir (haskell-debug-session)))
(let ((inhibit-read-only t)
(p (point)))
(erase-buffer)
(insert (propertize (concat "Debugging "
(haskell-session-name (haskell-debug-session))
"\n\n")
'face `((:weight bold))))
(let ((modules (haskell-debug-get-modules))
(breakpoints (haskell-debug-get-breakpoints))
(context (haskell-debug-get-context))
(history (haskell-debug-get-history)))
(unless modules
(insert (propertize "You have to load a module to start debugging."
'face
'haskell-debug-warning-face)
"\n\n"))
(haskell-debug-insert-bindings modules breakpoints context)
(when modules
(haskell-debug-insert-current-context context history)
(haskell-debug-insert-breakpoints breakpoints))
(haskell-debug-insert-modules modules))
(insert "\n")
(goto-char (min (point-max) p)))))
(defun haskell-debug/delete ()
"Delete whatever's at the point."
(interactive)
(cond
((get-text-property (point) 'break)
(let ((break (get-text-property (point) 'break)))
(haskell-mode-toggle-interactive-prompt-state)
(unwind-protect
(when (y-or-n-p (format "Delete breakpoint #%d?"
(plist-get break :number)))
(haskell-process-queue-sync-request
(haskell-debug-process)
(format ":delete %d"
(plist-get break :number)))
(haskell-debug/refresh))
(haskell-mode-toggle-interactive-prompt-state t))))))
(defun haskell-debug/trace ()
"Trace the expression."
(interactive)
(haskell-debug-with-modules
(haskell-debug-with-breakpoints
(let ((expr (read-from-minibuffer "Expression to trace: "
(haskell-ident-at-point))))
(haskell-process-queue-sync-request
(haskell-debug-process)
(concat ":trace " expr))
(message "Tracing expression: %s" expr)
(haskell-debug/refresh)))))
(defun haskell-debug/step (&optional expr)
"Step into the next function."
(interactive)
(haskell-debug-with-breakpoints
(let* ((breakpoints (haskell-debug-get-breakpoints))
(context (haskell-debug-get-context))
(string
(haskell-process-queue-sync-request
(haskell-debug-process)
(if expr
(concat ":step " expr)
":step"))))
(cond
((string= string "not stopped at a breakpoint\n")
(if haskell-debug-bindings-cache
(progn (setq haskell-debug-bindings-cache nil)
(haskell-debug/refresh))
(call-interactively 'haskell-debug/start-step)))
(t (let ((maybe-stopped-at (haskell-debug-parse-stopped-at string)))
(cond
(maybe-stopped-at
(setq haskell-debug-bindings-cache
maybe-stopped-at)
(message "Computation paused.")
(haskell-debug/refresh))
(t
(if context
(message "Computation finished.")
(progn
(haskell-mode-toggle-interactive-prompt-state)
(unwind-protect
(when (y-or-n-p "Computation completed without breaking. Reload the module and retry?")
(message "Reloading and resetting breakpoints...")
(haskell-interactive-mode-reset-error (haskell-debug-session))
(cl-loop for break in breakpoints
do (haskell-process-queue-sync-request
(haskell-debug-process)
(concat ":load " (plist-get break :path))))
(cl-loop for break in breakpoints
do (haskell-debug-break break))
(haskell-debug/step expr))
(haskell-mode-toggle-interactive-prompt-state t))))))))))
(haskell-debug/refresh)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Internal functions
(defun haskell-debug-session ()
"Get the Haskell session."
(or (haskell-session-maybe)
(error "No Haskell session associated with this debug
buffer. Please just close the buffer and start again.")))
(defun haskell-debug-process ()
"Get the Haskell session."
(or (haskell-session-process (haskell-session-maybe))
(error "No Haskell session associated with this debug
buffer. Please just close the buffer and start again.")))
(defun haskell-debug-buffer-name (session)
"The debug buffer name for the current session."
(format "*debug:%s*"
(haskell-session-name session)))
(defun haskell-debug-get-breakpoints ()
"Get the list of breakpoints currently set."
(let ((string (haskell-process-queue-sync-request
(haskell-debug-process)
":show breaks")))
(if (string= string "No active breakpoints.\n")
(list)
(mapcar #'haskell-debug-parse-break-point
(haskell-debug-split-string string)))))
(defun haskell-debug-get-modules ()
"Get the list of modules currently set."
(let ((string (haskell-process-queue-sync-request
(haskell-debug-process)
":show modules")))
(if (string= string "")
(list)
(mapcar #'haskell-debug-parse-module
(haskell-debug-split-string string)))))
(defun haskell-debug-get-context ()
"Get the current context."
(let ((string (haskell-process-queue-sync-request
(haskell-debug-process)
":show context")))
(if (string= string "")
nil
(haskell-debug-parse-context string))))
(defun haskell-debug-get-history ()
"Get the step history."
(let ((string (haskell-process-queue-sync-request
(haskell-debug-process)
":history")))
(if (or (string= string "")
(string= string "Not stopped at a breakpoint\n"))
nil
(if (string= string "Empty history. Perhaps you forgot to use :trace?\n")
nil
(let ((entries (mapcar #'haskell-debug-parse-history-entry
(cl-remove-if (lambda (line) (or (string= "<end of history>" line)
(string= "..." line)))
(haskell-debug-split-string string)))))
(setq haskell-debug-history-cache
entries)
entries)))))
(defun haskell-debug-insert-bindings (modules breakpoints context)
"Insert a list of bindings."
(if breakpoints
(progn (haskell-debug-insert-binding "t" "trace an expression")
(haskell-debug-insert-binding "s" "step into an expression")
(haskell-debug-insert-binding "b" "breakpoint" t))
(progn
(when modules
(haskell-debug-insert-binding "b" "breakpoint"))
(when breakpoints
(haskell-debug-insert-binding "s" "step into an expression" t))))
(when breakpoints
(haskell-debug-insert-binding "d" "delete breakpoint"))
(when context
(haskell-debug-insert-binding "a" "abandon context")
(haskell-debug-insert-binding "c" "continue" t))
(when context
(haskell-debug-insert-binding "p" "previous step")
(haskell-debug-insert-binding "n" "next step" t))
(haskell-debug-insert-binding "g" "refresh" t)
(insert "\n"))
(defun haskell-debug-insert-current-context (context history)
"Insert the current context."
(haskell-debug-insert-header "Context")
(if context
(haskell-debug-insert-context context history)
(haskell-debug-insert-debug-finished))
(insert "\n"))
(defun haskell-debug-insert-breakpoints (breakpoints)
"insert the list of breakpoints."
(haskell-debug-insert-header "Breakpoints")
(if (null breakpoints)
(haskell-debug-insert-muted "No active breakpoints.")
(cl-loop for break in breakpoints
do (insert (propertize (format "%d"
(plist-get break :number))
'face `((:weight bold))
'break break)
(haskell-debug-muted " - ")
(propertize (plist-get break :module)
'break break
'break break)
(haskell-debug-muted
(format " (%d:%d)"
(plist-get (plist-get break :span) :start-line)
(plist-get (plist-get break :span) :start-col)))
"\n")))
(insert "\n"))
(defun haskell-debug-insert-modules (modules)
"Insert the list of modules."
(haskell-debug-insert-header "Modules")
(if (null modules)
(haskell-debug-insert-muted "No loaded modules.")
(progn (cl-loop for module in modules
do (insert (propertize (plist-get module :module)
'module module
'face `((:weight bold)))
(haskell-debug-muted " - ")
(propertize (file-name-nondirectory (plist-get module :path))
'module module))
do (insert "\n")))))
(defun haskell-debug-split-string (string)
"Split GHCi's line-based output, stripping the trailing newline."
(split-string string "\n" t))
(defun haskell-debug-parse-context (string)
"Parse the context."
(cond
((string-match "^--> \\(.+\\)\n \\(.+\\)" string)
(let ((name (match-string 1 string))
(stopped (haskell-debug-parse-stopped-at (match-string 2 string))))
(list :name name
:path (plist-get stopped :path)
:span (plist-get stopped :span))))))
(defun haskell-debug-insert-binding (binding desc &optional end)
"Insert a helpful keybinding."
(insert (propertize binding 'face 'haskell-debug-keybinding-face)
(haskell-debug-muted " - ")
desc
(if end
"\n"
(haskell-debug-muted ", "))))
(defun haskell-debug-insert-header (title)
"Insert a header title."
(insert (propertize title
'face 'haskell-debug-heading-face)
"\n\n"))
(defun haskell-debug-insert-context (context history)
"Insert the context and history."
(when context
(insert (propertize (plist-get context :name) 'face `((:weight bold)))
(haskell-debug-muted " - ")
(file-name-nondirectory (plist-get context :path))
(haskell-debug-muted " (stopped)")
"\n"))
(when haskell-debug-bindings-cache
(insert "\n")
(let ((bindings haskell-debug-bindings-cache))
(insert
(haskell-debug-get-span-string
(plist-get bindings :path)
(plist-get bindings :span)))
(insert "\n\n")
(cl-loop for binding in (plist-get bindings :types)
do (insert (haskell-fontify-as-mode binding 'haskell-mode)
"\n"))))
(let ((history (or history
(list (haskell-debug-make-fake-history context)))))
(when history
(insert "\n")
(haskell-debug-insert-history history))))
(defun haskell-debug-insert-debug-finished ()
"Insert message that no debugging is happening, but if there is
some old history, then display that."
(if haskell-debug-history-cache
(progn (haskell-debug-insert-muted "Finished debugging.")
(insert "\n")
(haskell-debug-insert-history haskell-debug-history-cache))
(haskell-debug-insert-muted "Not debugging right now.")))
(defun haskell-debug-insert-muted (text)
"Insert some muted text."
(insert (haskell-debug-muted text)
"\n"))
(defun haskell-debug-muted (text)
"Make some muted text."
(propertize text 'face 'haskell-debug-muted-face))
(defun haskell-debug-parse-logged (string)
"Parse the logged breakpoint."
(cond
((string= "no more logged breakpoints\n" string)
nil)
((string= "already at the beginning of the history\n" string)
nil)
(t
(with-temp-buffer
(insert string)
(goto-char (point-min))
(list :path (progn (search-forward " at ")
(buffer-substring-no-properties
(point)
(1- (search-forward ":"))))
:span (haskell-debug-parse-span
(buffer-substring-no-properties
(point)
(line-end-position)))
:types (progn (forward-line)
(haskell-debug-split-string
(buffer-substring-no-properties
(point)
(point-max)))))))))
(defun haskell-debug-parse-stopped-at (string)
"Parse the location stopped at from the given string.
For example:
Stopped at /home/foo/project/src/x.hs:6:25-36
"
(let ((index (string-match "Stopped at \\([^:]+\\):\\(.+\\)\n?"
string)))
(when index
(list :path (match-string 1 string)
:span (haskell-debug-parse-span (match-string 2 string))
:types (cdr (haskell-debug-split-string (substring string index)))))))
(defun haskell-debug-get-span-string (path span)
"Get the string from the PATH and the SPAN."
(save-window-excursion
(find-file path)
(buffer-substring
(save-excursion
(goto-char (point-min))
(forward-line (1- (plist-get span :start-line)))
(forward-char (1- (plist-get span :start-col)))
(point))
(save-excursion
(goto-char (point-min))
(forward-line (1- (plist-get span :end-line)))
(forward-char (plist-get span :end-col))
(point)))))
(defun haskell-debug-make-fake-history (context)
"Make a fake history item."
(list :index -1
:path (plist-get context :path)
:span (plist-get context :span)))
(defun haskell-debug-insert-history (history)
"Insert tracing HISTORY."
(let ((i (length history)))
(cl-loop for span in history
do (let ((string (haskell-debug-get-span-string
(plist-get span :path)
(plist-get span :span))))
(insert (propertize (format "%4d" i)
'face 'haskell-debug-trace-number-face)
" "
(haskell-debug-preview-span
(plist-get span :span)
string
t)
"\n")
(setq i (1- i))))))
(defun haskell-debug-parse-span (string)
"Parse a source span from a string.
Examples:
(5,1)-(6,37)
6:25-36
5:20
People like to make other people's lives interesting by making
variances in source span notation."
(cond
((string-match "\\([0-9]+\\):\\([0-9]+\\)-\\([0-9]+\\)"
string)
(list :start-line (string-to-number (match-string 1 string))
:start-col (string-to-number (match-string 2 string))
:end-line (string-to-number (match-string 1 string))
:end-col (string-to-number (match-string 3 string))))
((string-match "\\([0-9]+\\):\\([0-9]+\\)"
string)
(list :start-line (string-to-number (match-string 1 string))
:start-col (string-to-number (match-string 2 string))
:end-line (string-to-number (match-string 1 string))
:end-col (string-to-number (match-string 2 string))))
((string-match "(\\([0-9]+\\),\\([0-9]+\\))-(\\([0-9]+\\),\\([0-9]+\\))"
string)
(list :start-line (string-to-number (match-string 1 string))
:start-col (string-to-number (match-string 2 string))
:end-line (string-to-number (match-string 3 string))
:end-col (string-to-number (match-string 4 string))))
(t (error "Unable to parse source span from string: %s"
string))))
(defun haskell-debug-preview-span (span string &optional collapsed)
"Make a one-line preview of the given expression."
(with-temp-buffer
(haskell-mode)
(insert string)
(when (/= 0 (plist-get span :start-col))
(indent-rigidly (point-min)
(point-max)
1))
(if (fboundp 'font-lock-ensure)
(font-lock-ensure)
(with-no-warnings (font-lock-fontify-buffer)))
(when (/= 0 (plist-get span :start-col))
(indent-rigidly (point-min)
(point-max)
-1))
(goto-char (point-min))
(if collapsed
(replace-regexp-in-string
"\n[ ]*"
(propertize " " 'face 'haskell-debug-newline-face)
(buffer-substring (point-min)
(point-max)))
(buffer-string))))
(defun haskell-debug-start (session)
"Start the debug mode."
(setq buffer-read-only t)
(haskell-session-assign session)
(haskell-debug/refresh))
(defun haskell-debug ()
"Start the debugger for the current Haskell (GHCi) session."
(interactive)
(let ((session (haskell-debug-session)))
(switch-to-buffer-other-window (haskell-debug-buffer-name session))
(unless (eq major-mode 'haskell-debug-mode)
(haskell-debug-mode)
(haskell-debug-start session))))
(defun haskell-debug-break (break)
"Set BREAK breakpoint in module at line/col."
(haskell-process-queue-without-filters
(haskell-debug-process)
(format ":break %s %s %d"
(plist-get break :module)
(plist-get (plist-get break :span) :start-line)
(plist-get (plist-get break :span) :start-col))))
(defun haskell-debug-navigate (direction)
"Navigate in DIRECTION \"back\" or \"forward\"."
(let ((string (haskell-process-queue-sync-request
(haskell-debug-process)
(concat ":" direction))))
(let ((bindings (haskell-debug-parse-logged string)))
(setq haskell-debug-bindings-cache
bindings)
(when (not bindings)
(message "No more %s results!" direction)))
(haskell-debug/refresh)))
(defun haskell-debug-session-debugging-p (session)
"Does the session have a debugging buffer open?"
(not (not (get-buffer (haskell-debug-buffer-name session)))))
(defun haskell-debug-highlight (path &optional span)
"Highlight the file at span."
(let ((p (make-overlay
(line-beginning-position)
(line-end-position))))
(overlay-put p 'face `((:background "#eee")))
(with-current-buffer
(if span
(save-window-excursion
(find-file path)
(current-buffer))
(find-file path)
(current-buffer))
(let ((o (when span
(make-overlay
(save-excursion
(goto-char (point-min))
(forward-line (1- (plist-get span :start-line)))
(forward-char (1- (plist-get span :start-col)))
(point))
(save-excursion
(goto-char (point-min))
(forward-line (1- (plist-get span :end-line)))
(forward-char (plist-get span :end-col))
(point))))))
(when o
(overlay-put o 'face `((:background "#eee"))))
(sit-for 0.5)
(when o
(delete-overlay o))
(delete-overlay p)))))
(defun haskell-debug-parse-history-entry (string)
"Parse a history entry."
(if (string-match "^\\([-0-9]+\\)[ ]+:[ ]+\\([A-Za-z0-9_':]+\\)[ ]+(\\([^:]+\\):\\(.+?\\))$"
string)
(list :index (string-to-number (match-string 1 string))
:name (match-string 2 string)
:path (match-string 3 string)
:span (haskell-debug-parse-span (match-string 4 string)))
(error "Unable to parse history entry: %s" string)))
(defun haskell-debug-parse-module (string)
"Parse a module and path.
For example:
X ( /home/foo/X.hs, interpreted )
Main ( /home/foo/X.hs, /home/foo/X.o )
"
(if (string-match "\\([^ ]+\\)[ ]+( \\([^ ]+?\\), [/a-zA-Z0-9\.]+ )$"
string)
(list :module (match-string 1 string)
:path (match-string 2 string))
(error "Unable to parse module from string: %s"
string)))
(defun haskell-debug-parse-break-point (string)
"Parse a breakpoint number, module and location from a string.
For example:
[13] Main /home/foo/src/x.hs:(5,1)-(6,37)
"
(if (string-match "^\\[\\([0-9]+\\)\\] \\([^ ]+\\) \\([^:]+\\):\\(.+\\)$"
string)
(list :number (string-to-number (match-string 1 string))
:module (match-string 2 string)
:path (match-string 3 string)
:span (haskell-debug-parse-span (match-string 4 string)))
(error "Unable to parse breakpoint from string: %s"
string)))
(provide 'haskell-debug)
;;; haskell-debug.el ends here

Binary file not shown.

View File

@@ -0,0 +1,687 @@
;;; haskell-decl-scan.el --- Declaration scanning module for Haskell Mode -*- lexical-binding: t -*-
;; Copyright (C) 2004, 2005, 2007, 2009 Free Software Foundation, Inc.
;; Copyright (C) 1997-1998 Graeme E Moss
;; Copyright (C) 2016 Chris Gregory
;; Author: 1997-1998 Graeme E Moss <gem@cs.york.ac.uk>
;; Maintainer: Stefan Monnier <monnier@gnu.org>
;; Keywords: declarations menu files Haskell
;; URL: http://cvs.haskell.org/cgi-bin/cvsweb.cgi/fptools/CONTRIB/haskell-modes/emacs/haskell-decl-scan.el?rev=HEAD
;; This file is not part of GNU Emacs.
;; 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 this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; Purpose:
;;
;; Top-level declarations are scanned and placed in a menu. Supports
;; full Latin1 Haskell 1.4 as well as literate scripts.
;;
;;
;; Installation:
;;
;; To turn declaration scanning on for all Haskell buffers under the
;; Haskell mode of Moss&Thorn, add this to .emacs:
;;
;; (add-hook 'haskell-mode-hook 'haskell-decl-scan-mode)
;;
;; Otherwise, call `haskell-decl-scan-mode'.
;;
;;
;; Customisation:
;;
;; M-x customize-group haskell-decl-scan
;;
;;
;; History:
;;
;; If you have any problems or suggestions, after consulting the list
;; below, email gem@cs.york.ac.uk quoting the version of the library
;; you are using, the version of Emacs you are using, and a small
;; example of the problem or suggestion. Note that this library
;; requires a reasonably recent version of Emacs.
;;
;; Uses `imenu' under Emacs.
;;
;; Version 1.2:
;; Added support for LaTeX-style literate scripts.
;;
;; Version 1.1:
;; Use own syntax table. Fixed bug for very small buffers. Use
;; markers instead of pointers (markers move with the text).
;;
;; Version 1.0:
;; Brought over from Haskell mode v1.1.
;;
;;
;; Present Limitations/Future Work (contributions are most welcome!):
;;
;; . Declarations requiring information extending beyond starting line
;; don't get scanned properly, eg.
;; > class Eq a =>
;; > Test a
;;
;; . Comments placed in the midst of the first few lexemes of a
;; declaration will cause havoc, eg.
;; > infixWithComments :: Int -> Int -> Int
;; > x {-nastyComment-} `infixWithComments` y = x + y
;; but are not worth worrying about.
;;
;; . Would be nice to scan other top-level declarations such as
;; methods of a class, datatype field labels... any more?
;;
;; . Support for GreenCard?
;;
;; . Re-running (literate-)haskell-imenu should not cause the problems
;; that it does. The ability to turn off scanning would also be
;; useful. (Note that re-running (literate-)haskell-mode seems to
;; cause no problems.)
;; All functions/variables start with
;; `(turn-(on/off)-)haskell-decl-scan' or `haskell-ds-'.
;; The imenu support is based on code taken from `hugs-mode',
;; thanks go to Chris Van Humbeeck.
;; Version.
;;; Code:
(require 'cl-lib)
(require 'haskell-mode)
(require 'syntax)
(require 'imenu)
;;;###autoload
(defgroup haskell-decl-scan nil
"Haskell declaration scanning (`imenu' support)."
:link '(custom-manual "(haskell-mode)haskell-decl-scan-mode")
:group 'haskell
:prefix "haskell-decl-scan-")
(defcustom haskell-decl-scan-bindings-as-variables nil
"Whether to put top-level value bindings into a \"Variables\" category."
:group 'haskell-decl-scan
:type 'boolean)
(defcustom haskell-decl-scan-add-to-menubar t
"Whether to add a \"Declarations\" menu entry to menu bar."
:group 'haskell-decl-scan
:type 'boolean)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; General declaration scanning functions.
(defvar haskell-ds-start-keywords-re
(concat "\\(\\<"
"class\\|data\\|i\\(mport\\|n\\(fix\\(\\|[lr]\\)\\|stance\\)\\)\\|"
"module\\|primitive\\|type\\|newtype"
"\\)\\>")
"Keywords that may start a declaration.")
(defvar haskell-ds-syntax-table
(let ((table (copy-syntax-table haskell-mode-syntax-table)))
(modify-syntax-entry ?\' "w" table)
(modify-syntax-entry ?_ "w" table)
(modify-syntax-entry ?\\ "_" table)
table)
"Syntax table used for Haskell declaration scanning.")
(defun haskell-ds-get-variable (prefix)
"Return variable involved in value binding or type signature.
Assumes point is looking at the regexp PREFIX followed by the
start of a declaration (perhaps in the middle of a series of
declarations concerning a single variable). Otherwise return nil.
Point is not changed."
;; I think I can now handle all declarations bar those with comments
;; nested before the second lexeme.
(save-excursion
(with-syntax-table haskell-ds-syntax-table
(if (looking-at prefix) (goto-char (match-end 0)))
;; Keyword.
(if (looking-at haskell-ds-start-keywords-re)
nil
(or ;; Parenthesized symbolic variable.
(and (looking-at "(\\(\\s_+\\))") (match-string-no-properties 1))
;; General case.
(if (looking-at
(if (eq ?\( (char-after))
;; Skip paranthesised expression.
(progn
(forward-sexp)
;; Repeating this code and avoiding moving point if
;; possible speeds things up.
"\\(\\'\\)?\\s-*\\(\\s_+\\|`\\(\\sw+\\)`\\)")
"\\(\\sw+\\)?\\s-*\\(\\s_+\\|`\\(\\sw+\\)`\\)"))
(let ((match2 (match-string-no-properties 2)))
;; Weed out `::', `∷',`=' and `|' from potential infix
;; symbolic variable.
(if (member match2 '("::" "" "=" "|"))
;; Variable identifier.
(match-string-no-properties 1)
(if (eq (aref match2 0) ?\`)
;; Infix variable identifier.
(match-string-no-properties 3)
;; Infix symbolic variable.
match2))))
;; Variable identifier.
(and (looking-at "\\sw+") (match-string-no-properties 0)))))))
(defun haskell-ds-move-to-start-regexp (inc regexp)
"Move to beginning of line that succeeds/precedes (INC = 1/-1)
current line that starts with REGEXP and is not in `font-lock-comment-face'."
;; Making this defsubst instead of defun appears to have little or
;; no effect on efficiency. It is probably not called enough to do
;; so.
(while (and (= (forward-line inc) 0)
(or (not (looking-at regexp))
(eq (get-text-property (point) 'face)
'font-lock-comment-face)))))
(defun haskell-ds-move-to-start-regexp-skipping-comments (inc regexp)
"Like haskell-ds-move-to-start-regexp, but uses syntax-ppss to
skip comments"
(let (p)
(cl-loop
do (setq p (point))
(haskell-ds-move-to-start-regexp inc regexp)
while (and (nth 4 (syntax-ppss))
(/= p (point))))))
(defvar literate-haskell-ds-line-prefix "> ?"
"Regexp matching start of a line of Bird-style literate code.
Current value is \"> \" as we assume top-level declarations start
at column 3. Must not contain the special \"^\" regexp as we may
not use the regexp at the start of a regexp string. Note this is
only for `imenu' support.")
(defvar haskell-ds-start-decl-re "\\(\\sw\\|(\\)"
"The regexp that starts a Haskell declaration.")
(defvar literate-haskell-ds-start-decl-re
(concat literate-haskell-ds-line-prefix haskell-ds-start-decl-re)
"The regexp that starts a Bird-style literate Haskell declaration.")
(defun haskell-ds-whitespace-p (char)
"Test if CHAR is a whitespace character."
;; the nil is a bob/eob test
(member char '(nil ?\t ?\n ?\ )))
(defun haskell-ds-move-to-decl (direction bird-literate fix)
"General function for moving to the start of a declaration,
either forwards or backwards from point, with normal or with Bird-style
literate scripts. If DIRECTION is t, then forward, else backward. If
BIRD-LITERATE is t, then treat as Bird-style literate scripts, else
normal scripts. Returns point if point is left at the start of a
declaration, and nil otherwise, ie. because point is at the beginning
or end of the buffer and no declaration starts there. If FIX is t,
then point does not move if already at the start of a declaration."
;; As `haskell-ds-get-variable' cannot separate an infix variable
;; identifier out of a value binding with non-alphanumeric first
;; argument, this function will treat such value bindings as
;; separate from the declarations surrounding it.
(let ( ;; The variable typed or bound in the current series of
;; declarations.
name
;; The variable typed or bound in the new declaration.
newname
;; Hack to solve hard problem for Bird-style literate scripts
;; that start with a declaration. We are in the abyss if
;; point is before start of this declaration.
abyss
(line-prefix (if bird-literate literate-haskell-ds-line-prefix ""))
;; The regexp to match for the start of a declaration.
(start-decl-re (if bird-literate
literate-haskell-ds-start-decl-re
haskell-ds-start-decl-re))
(increment (if direction 1 -1))
(bound (if direction (point-max) (point-min))))
;; Change syntax table.
(with-syntax-table haskell-ds-syntax-table
;; move to beginning of line that starts the "current
;; declaration" (dependent on DIRECTION and FIX), and then get
;; the variable typed or bound by this declaration, if any.
(let ( ;; Where point was at call of function.
(here (point))
;; Where the declaration on this line (if any) starts.
(start (progn
(beginning-of-line)
;; Checking the face to ensure a declaration starts
;; here seems to be the only addition to make this
;; module support LaTeX-style literate scripts.
(if (and (looking-at start-decl-re)
(not (elt (syntax-ppss) 4)))
(match-beginning 1)))))
(if (and start
;; This complicated boolean determines whether we
;; should include the declaration that starts on the
;; current line as the "current declaration" or not.
(or (and (or (and direction (not fix))
(and (not direction) fix))
(>= here start))
(and (or (and direction fix)
(and (not direction) (not fix)))
(> here start))))
;; If so, we are already at start of the current line, so
;; do nothing.
()
;; If point was before start of a declaration on the first
;; line of the buffer (possible for Bird-style literate
;; scripts) then we are in the abyss.
(if (and start (bobp))
(setq abyss t)
;; Otherwise we move to the start of the first declaration
;; on a line preceding the current one, skipping comments
(haskell-ds-move-to-start-regexp-skipping-comments -1 start-decl-re))))
;; If we are in the abyss, position and return as appropriate.
(if abyss
(if (not direction)
nil
(re-search-forward (concat "\\=" line-prefix) nil t)
(point))
;; Get the variable typed or bound by this declaration, if any.
(setq name (haskell-ds-get-variable line-prefix))
(if (not name)
;; If no such variable, stop at the start of this
;; declaration if moving backward, or move to the next
;; declaration if moving forward.
(if direction
(haskell-ds-move-to-start-regexp-skipping-comments 1 start-decl-re))
;; If there is a variable, find the first
;; succeeding/preceding declaration that does not type or
;; bind it. Check for reaching start/end of buffer and
;; comments.
(haskell-ds-move-to-start-regexp-skipping-comments increment start-decl-re)
(while (and (/= (point) bound)
(and (setq newname (haskell-ds-get-variable line-prefix))
(string= name newname)))
(setq name newname)
(haskell-ds-move-to-start-regexp-skipping-comments increment start-decl-re))
;; If we are going backward, and have either reached a new
;; declaration or the beginning of a buffer that does not
;; start with a declaration, move forward to start of next
;; declaration (which must exist). Otherwise, we are done.
(if (and (not direction)
(or (and (looking-at start-decl-re)
(not (string= name
;; Note we must not use
;; newname here as this may
;; not have been set if we
;; have reached the beginning
;; of the buffer.
(haskell-ds-get-variable
line-prefix))))
(and (not (looking-at start-decl-re))
(bobp))))
(haskell-ds-move-to-start-regexp-skipping-comments 1 start-decl-re)))
;; Store whether we are at the start of a declaration or not.
;; Used to calculate final result.
(let ((at-start-decl (looking-at start-decl-re)))
;; If we are at the beginning of a line, move over
;; line-prefix, if present at point.
(if (bolp)
(re-search-forward (concat "\\=" line-prefix) (point-max) t))
;; Return point if at the start of a declaration and nil
;; otherwise.
(if at-start-decl (point) nil))))))
(defun haskell-ds-bird-p ()
(and (boundp 'haskell-literate) (eq haskell-literate 'bird)))
(defun haskell-ds-backward-decl ()
"Move backward to the first character that starts a top-level declaration.
A series of declarations concerning one variable is treated as one
declaration by this function. So, if point is within a top-level
declaration then move it to the start of that declaration. If point
is already at the start of a top-level declaration, then move it to
the start of the preceding declaration. Returns point if point is
left at the start of a declaration, and nil otherwise, because
point is at the beginning of the buffer and no declaration starts
there."
(interactive)
(haskell-ds-move-to-decl nil (haskell-ds-bird-p) nil))
(defun haskell-ds-comment-p
(&optional
pt)
"Test if the cursor is on whitespace or a comment.
`PT' defaults to `(point)'"
;; ensure result is `t' or `nil' instead of just truthy
(if (or
;; is cursor on whitespace
(haskell-ds-whitespace-p (following-char))
;; http://emacs.stackexchange.com/questions/14269/how-to-detect-if-the-point-is-within-a-comment-area
;; is cursor at begging, inside, or end of comment
(let ((fontfaces (get-text-property (or pt
(point)) 'face)))
(when (not (listp fontfaces))
(setf fontfaces (list fontfaces)))
(delq nil (mapcar
#'(lambda (f)
(member f '(font-lock-comment-face
font-lock-doc-face
font-lock-comment-delimiter-face)))
fontfaces))))
t
nil))
(defun haskell-ds-line-commented-p ()
"Tests if all characters from `point' to `end-of-line' pass
`haskell-ds-comment-p'"
(let ((r t))
(while (and r (not (eolp)))
(if (not (haskell-ds-comment-p))
(setq r nil))
(forward-char))
r))
(defun haskell-ds-forward-decl ()
"Move forward to the first character that starts a top-level
declaration. As `haskell-ds-backward-decl' but forward."
(interactive)
(let ((p (point)) b e empty was-at-bob)
;; Go back to beginning of defun, then go to beginning of next
(haskell-ds-move-to-decl nil (haskell-ds-bird-p) nil)
(setq b (point))
(haskell-ds-move-to-decl t (haskell-ds-bird-p) nil)
(setq e (point))
;; tests if line is empty
(setq empty (and (<= (point) p)
(not (eolp))))
(setq was-at-bob (and (= (point-min) b)
(= b p)
(< p e)))
;; this conditional allows for when empty lines at end, first
;; `C-M-e' will go to end of defun, next will go to end of file.
(when (or was-at-bob
empty)
(if (or (and was-at-bob
(= ?\n
(save-excursion
(goto-char (point-min))
(following-char))))
empty)
(haskell-ds-move-to-decl t (haskell-ds-bird-p) nil))
;; Then go back to end of current
(forward-line -1)
(while (and (haskell-ds-line-commented-p)
;; prevent infinite loop
(not (bobp)))
(forward-line -1))
(forward-line 1)))
(point))
(defun haskell-ds-generic-find-next-decl (bird-literate)
"Find the name, position and type of the declaration at or after point.
Return ((NAME . (START-POSITION . NAME-POSITION)) . TYPE)
if one exists and nil otherwise. The start-position is at the start
of the declaration, and the name-position is at the start of the name
of the declaration. The name is a string, the positions are buffer
positions and the type is one of the symbols \"variable\", \"datatype\",
\"class\", \"import\" and \"instance\"."
(let ( ;; The name, type and name-position of the declaration to
;; return.
name
type
name-pos
;; Buffer positions marking the start and end of the space
;; containing a declaration.
start
end)
;; Change to declaration scanning syntax.
(with-syntax-table haskell-ds-syntax-table
;; Stop when we are at the end of the buffer or when a valid
;; declaration is grabbed.
(while (not (or (eobp) name))
;; Move forward to next declaration at or after point.
(haskell-ds-move-to-decl t bird-literate t)
;; Start and end of search space is currently just the starting
;; line of the declaration.
(setq start (point)
end (line-end-position))
(cond
;; If the start of the top-level declaration does not begin
;; with a starting keyword, then (if legal) must be a type
;; signature or value binding, and the variable concerned is
;; grabbed.
((not (looking-at haskell-ds-start-keywords-re))
(setq name (haskell-ds-get-variable ""))
(if name
(progn
(setq type 'variable)
(re-search-forward (regexp-quote name) end t)
(setq name-pos (match-beginning 0)))))
;; User-defined datatype declaration.
((re-search-forward "\\=\\(data\\|newtype\\|type\\)\\>" end t)
(re-search-forward "=>" end t)
(if (looking-at "[ \t]*\\(\\sw+\\)")
(progn
(setq name (match-string-no-properties 1))
(setq name-pos (match-beginning 1))
(setq type 'datatype))))
;; Class declaration.
((re-search-forward "\\=class\\>" end t)
(re-search-forward "=>" end t)
(if (looking-at "[ \t]*\\(\\sw+\\)")
(progn
(setq name (match-string-no-properties 1))
(setq name-pos (match-beginning 1))
(setq type 'class))))
;; Import declaration.
((looking-at "import[ \t]+\\(?:safe[\t ]+\\)?\\(?:qualified[ \t]+\\)?\\(?:\"[^\"]*\"[\t ]+\\)?\\(\\(?:\\sw\\|.\\)+\\)")
(setq name (match-string-no-properties 1))
(setq name-pos (match-beginning 1))
(setq type 'import))
;; Instance declaration.
((re-search-forward "\\=instance[ \t]+" end t)
(re-search-forward "=>[ \t]+" end t)
;; The instance "title" starts just after the `instance' (and
;; any context) and finishes just before the _first_ `where'
;; if one exists. This solution is ugly, but I can't find a
;; nicer one---a simple regexp will pick up the last `where',
;; which may be rare but nevertheless...
(setq name-pos (point))
(setq name (buffer-substring-no-properties
(point)
(progn
;; Look for a `where'.
(if (re-search-forward "\\<where\\>" end t)
;; Move back to just before the `where'.
(progn
(re-search-backward "\\s-where")
(point))
;; No `where' so move to last non-whitespace
;; before `end'.
(progn
(goto-char end)
(skip-chars-backward " \t")
(point))))))
;; If we did not manage to extract a name, cancel this
;; declaration (eg. when line ends in "=> ").
(if (string-match "^[ \t]*$" name) (setq name nil))
(setq type 'instance)))
;; Move past start of current declaration.
(goto-char end))
;; If we have a valid declaration then return it, otherwise return
;; nil.
(if name
(cons (cons name (cons (copy-marker start t) (copy-marker name-pos t)))
type)
nil))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Declaration scanning via `imenu'.
;;;###autoload
(defun haskell-ds-create-imenu-index ()
"Function for finding `imenu' declarations in Haskell mode.
Finds all declarations (classes, variables, imports, instances and
datatypes) in a Haskell file for the `imenu' package."
;; Each list has elements of the form `(INDEX-NAME . INDEX-POSITION)'.
;; These lists are nested using `(INDEX-TITLE . INDEX-ALIST)'.
(let* ((bird-literate (haskell-ds-bird-p))
(index-alist '())
(index-class-alist '()) ;; Classes
(index-var-alist '()) ;; Variables
(index-imp-alist '()) ;; Imports
(index-inst-alist '()) ;; Instances
(index-type-alist '()) ;; Datatypes
;; Variables for showing progress.
(bufname (buffer-name))
(divisor-of-progress (max 1 (/ (buffer-size) 100)))
;; The result we wish to return.
result)
(goto-char (point-min))
;; Loop forwards from the beginning of the buffer through the
;; starts of the top-level declarations.
(while (< (point) (point-max))
(message "Scanning declarations in %s... (%3d%%)" bufname
(/ (- (point) (point-min)) divisor-of-progress))
;; Grab the next declaration.
(setq result (haskell-ds-generic-find-next-decl bird-literate))
(if result
;; If valid, extract the components of the result.
(let* ((name-posns (car result))
(name (car name-posns))
(posns (cdr name-posns))
(start-pos (car posns))
(type (cdr result)))
;; Place `(name . start-pos)' in the correct alist.
(cl-case type
(variable
(setq index-var-alist
(cl-acons name start-pos index-var-alist)))
(datatype
(setq index-type-alist
(cl-acons name start-pos index-type-alist)))
(class
(setq index-class-alist
(cl-acons name start-pos index-class-alist)))
(import
(setq index-imp-alist
(cl-acons name start-pos index-imp-alist)))
(instance
(setq index-inst-alist
(cl-acons name start-pos index-inst-alist)))))))
;; Now sort all the lists, label them, and place them in one list.
(message "Sorting declarations in %s..." bufname)
(when index-type-alist
(push (cons "Datatypes"
(sort index-type-alist 'haskell-ds-imenu-label-cmp))
index-alist))
(when index-inst-alist
(push (cons "Instances"
(sort index-inst-alist 'haskell-ds-imenu-label-cmp))
index-alist))
(when index-imp-alist
(push (cons "Imports"
(sort index-imp-alist 'haskell-ds-imenu-label-cmp))
index-alist))
(when index-class-alist
(push (cons "Classes"
(sort index-class-alist 'haskell-ds-imenu-label-cmp))
index-alist))
(when index-var-alist
(if haskell-decl-scan-bindings-as-variables
(push (cons "Variables"
(sort index-var-alist 'haskell-ds-imenu-label-cmp))
index-alist)
(setq index-alist (append index-alist
(sort index-var-alist 'haskell-ds-imenu-label-cmp)))))
(message "Sorting declarations in %s...done" bufname)
;; Return the alist.
index-alist))
(defun haskell-ds-imenu-label-cmp (el1 el2)
"Predicate to compare labels in lists from `haskell-ds-create-imenu-index'."
(string< (car el1) (car el2)))
(defun haskell-ds-imenu ()
"Install `imenu' for Haskell scripts."
(setq imenu-create-index-function 'haskell-ds-create-imenu-index)
(when haskell-decl-scan-add-to-menubar
(imenu-add-to-menubar "Declarations")))
;; The main functions to turn on declaration scanning.
;;;###autoload
(defun turn-on-haskell-decl-scan ()
"Unconditionally activate `haskell-decl-scan-mode'."
(interactive)
(haskell-decl-scan-mode))
(make-obsolete 'turn-on-haskell-decl-scan
'haskell-decl-scan-mode
"2015-07-23")
;;;###autoload
(define-minor-mode haskell-decl-scan-mode
"Toggle Haskell declaration scanning minor mode on or off.
With a prefix argument ARG, enable minor mode if ARG is
positive, and disable it otherwise. If called from Lisp, enable
the mode if ARG is omitted or nil, and toggle it if ARG is `toggle'.
See also info node `(haskell-mode)haskell-decl-scan-mode' for
more details about this minor mode.
Top-level declarations are scanned and listed in the menu item
\"Declarations\" (if enabled via option
`haskell-decl-scan-add-to-menubar'). Selecting an item from this
menu will take point to the start of the declaration.
\\[beginning-of-defun] and \\[end-of-defun] move forward and backward to the start of a declaration.
This may link with `haskell-doc-mode'.
For non-literate and LaTeX-style literate scripts, we assume the
common convention that top-level declarations start at the first
column. For Bird-style literate scripts, we assume the common
convention that top-level declarations start at the third column,
ie. after \"> \".
Anything in `font-lock-comment-face' is not considered for a
declaration. Therefore, using Haskell font locking with comments
coloured in `font-lock-comment-face' improves declaration scanning.
Literate Haskell scripts are supported: If the value of
`haskell-literate' (set automatically by `literate-haskell-mode')
is `bird', a Bird-style literate script is assumed. If it is nil
or `tex', a non-literate or LaTeX-style literate script is
assumed, respectively.
Invokes `haskell-decl-scan-mode-hook' on activation."
:group 'haskell-decl-scan
(kill-local-variable 'beginning-of-defun-function)
(kill-local-variable 'end-of-defun-function)
(kill-local-variable 'imenu-create-index-function)
(unless haskell-decl-scan-mode
;; How can we cleanly remove the "Declarations" menu?
(when haskell-decl-scan-add-to-menubar
(local-set-key [menu-bar index] nil)))
(when haskell-decl-scan-mode
(setq-local beginning-of-defun-function 'haskell-ds-backward-decl)
(setq-local end-of-defun-function 'haskell-ds-forward-decl)
(haskell-ds-imenu)))
;; Provide ourselves:
(provide 'haskell-decl-scan)
;;; haskell-decl-scan.el ends here

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -0,0 +1,711 @@
;;; haskell-font-lock.el --- Font locking module for Haskell Mode -*- lexical-binding: t -*-
;; Copyright 2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
;; Copyright 1997-1998 Graeme E Moss, and Tommy Thorn
;; Author: 1997-1998 Graeme E Moss <gem@cs.york.ac.uk>
;; 1997-1998 Tommy Thorn <thorn@irisa.fr>
;; 2003 Dave Love <fx@gnu.org>
;; Keywords: faces files Haskell
;; This file is not part of GNU Emacs.
;; 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 this program. If not, see <http://www.gnu.org/licenses/>.
;;; Code:
(require 'cl-lib)
(require 'haskell-compat)
(require 'haskell-lexeme)
(require 'font-lock)
;;;###autoload
(defgroup haskell-appearance nil
"Haskell Appearance."
:group 'haskell)
(defcustom haskell-font-lock-symbols nil
"Display \\ and -> and such using symbols in fonts.
This may sound like a neat trick, but be extra careful: it changes the
alignment and can thus lead to nasty surprises with regards to layout."
:group 'haskell-appearance
:type 'boolean)
(defcustom haskell-font-lock-symbols-alist
'(("\\" . "λ")
("not" . "¬")
("->" . "")
("<-" . "")
("=>" . "")
("()" . "")
("==" . "")
("/=" . "")
(">=" . "")
("<=" . "")
("!!" . "")
("&&" . "")
("||" . "")
("sqrt" . "")
("undefined" . "")
("pi" . "π")
("~>" . "") ;; Omega language
;; ("~>" "↝") ;; less desirable
("-<" . "") ;; Paterson's arrow syntax
;; ("-<" "⤙") ;; nicer but uncommon
("::" . "")
("." "" ; "○"
;; Need a predicate here to distinguish the . used by
;; forall <foo> . <bar>.
haskell-font-lock-dot-is-not-composition)
("forall" . ""))
"Alist mapping Haskell symbols to chars.
Each element has the form (STRING . COMPONENTS) or (STRING
COMPONENTS PREDICATE).
STRING is the Haskell symbol.
COMPONENTS is a representation specification suitable as an argument to
`compose-region'.
PREDICATE if present is a function of one argument (the start position
of the symbol) which should return non-nil if this mapping should
be disabled at that position."
:type '(alist string string)
:group 'haskell-appearance)
(defcustom haskell-font-lock-keywords
;; `as', `hiding', and `qualified' are part of the import
;; spec syntax, but they are not reserved.
;; `_' can go in here since it has temporary word syntax.
'("case" "class" "data" "default" "deriving" "do"
"else" "if" "import" "in" "infix" "infixl"
"infixr" "instance" "let" "module" "mdo" "newtype" "of"
"rec" "pattern" "proc" "then" "type" "where" "_")
"Identifiers treated as reserved keywords in Haskell."
:group 'haskell-appearance
:type '(repeat string))
(defun haskell-font-lock-dot-is-not-composition (start)
"Return non-nil if the \".\" at START is not a composition operator.
This is the case if the \".\" is part of a \"forall <tvar> . <type>\"."
(save-excursion
(goto-char start)
(or (re-search-backward "\\<forall\\>[^.\"]*\\="
(line-beginning-position) t)
(not (or
(string= " " (string (char-after start)))
(null (char-before start))
(string= " " (string (char-before start))))))))
(defvar haskell-yesod-parse-routes-mode-keywords
'(("^\\([^ \t\n]+\\)\\(?:[ \t]+\\([^ \t\n]+\\)\\)?"
(1 'font-lock-string-face)
(2 'haskell-constructor-face nil lax))))
(define-derived-mode haskell-yesod-parse-routes-mode text-mode "Yesod parseRoutes mode"
"Mode for parseRoutes from Yesod."
(setq-local font-lock-defaults '(haskell-yesod-parse-routes-mode-keywords t t nil nil)))
(defcustom haskell-font-lock-quasi-quote-modes
`(("hsx" . xml-mode)
("hamlet" . shakespeare-hamlet-mode)
("shamlet" . shakespeare-hamlet-mode)
("whamlet" . shakespeare-hamlet-mode)
("xmlQQ" . xml-mode)
("xml" . xml-mode)
("cmd" . shell-mode)
("sh_" . shell-mode)
("jmacro" . javascript-mode)
("jmacroE" . javascript-mode)
("r" . ess-mode)
("rChan" . ess-mode)
("sql" . sql-mode)
("json" . json-mode)
("aesonQQ" . json-mode)
("parseRoutes" . haskell-yesod-parse-routes-mode))
"Mapping from quasi quoter token to fontification mode.
If a quasi quote is seen in Haskell code its contents will have
font faces assigned as if respective mode was enabled."
:group 'haskell-appearance
:type '(repeat (cons string symbol)))
;;;###autoload
(defface haskell-keyword-face
'((t :inherit font-lock-keyword-face))
"Face used to highlight Haskell keywords."
:group 'haskell-appearance)
;;;###autoload
(defface haskell-type-face
'((t :inherit font-lock-type-face))
"Face used to highlight Haskell types"
:group 'haskell-appearance)
;;;###autoload
(defface haskell-constructor-face
'((t :inherit font-lock-type-face))
"Face used to highlight Haskell constructors."
:group 'haskell-appearance)
;; This used to be `font-lock-variable-name-face' but it doesn't result in
;; a highlighting that's consistent with other modes (it's mostly used
;; for function defintions).
(defface haskell-definition-face
'((t :inherit font-lock-function-name-face))
"Face used to highlight Haskell definitions."
:group 'haskell-appearance)
;; This is probably just wrong, but it used to use
;; `font-lock-function-name-face' with a result that was not consistent with
;; other major modes, so I just exchanged with `haskell-definition-face'.
;;;###autoload
(defface haskell-operator-face
'((t :inherit font-lock-variable-name-face))
"Face used to highlight Haskell operators."
:group 'haskell-appearance)
;;;###autoload
(defface haskell-pragma-face
'((t :inherit font-lock-preprocessor-face))
"Face used to highlight Haskell pragmas ({-# ... #-})."
:group 'haskell-appearance)
;;;###autoload
(defface haskell-liquid-haskell-annotation-face
'((t :inherit haskell-pragma-face))
"Face used to highlight LiquidHaskell annotations ({-@ ... @-})."
:group 'haskell-appearance)
;;;###autoload
(defface haskell-literate-comment-face
'((t :inherit font-lock-doc-face))
"Face with which to fontify literate comments.
Inherit from `default' to avoid fontification of them."
:group 'haskell-appearance)
(defface haskell-quasi-quote-face
'((t :inherit font-lock-string-face))
"Generic face for quasiquotes.
Some quote types are fontified according to other mode defined in
`haskell-font-lock-quasi-quote-modes'."
:group 'haskell-appearance)
(defun haskell-font-lock-compose-symbol (alist)
"Compose a sequence of ascii chars into a symbol.
Regexp match data 0 points to the chars."
;; Check that the chars should really be composed into a symbol.
(let* ((start (match-beginning 0))
(end (match-end 0))
(syntaxes (cond
((eq (char-syntax (char-after start)) ?w) '(?w))
((eq (char-syntax (char-after start)) ?.) '(?.))
;; Special case for the . used for qualified names.
((and (eq (char-after start) ?\.) (= end (1+ start)))
'(?_ ?\\ ?w))
(t '(?_ ?\\))))
sym-data)
(if (or (memq (char-syntax (or (char-before start) ?\ )) syntaxes)
(memq (char-syntax (or (char-after end) ?\ )) syntaxes)
(or (elt (syntax-ppss) 3) (elt (syntax-ppss) 4))
(and (consp (setq sym-data (cdr (assoc (match-string 0) alist))))
(let ((pred (cadr sym-data)))
(setq sym-data (car sym-data))
(funcall pred start))))
;; No composition for you. Let's actually remove any composition
;; we may have added earlier and which is now incorrect.
(remove-text-properties start end '(composition))
;; That's a symbol alright, so add the composition.
(compose-region start end sym-data)))
;; Return nil because we're not adding any face property.
nil)
(defun haskell-font-lock-symbols-keywords ()
(when (and haskell-font-lock-symbols
haskell-font-lock-symbols-alist)
`((,(regexp-opt (mapcar 'car haskell-font-lock-symbols-alist) t)
(0 (haskell-font-lock-compose-symbol ',haskell-font-lock-symbols-alist)
;; In Emacs-21, if the `override' field is nil, the face
;; expressions is only evaluated if the text has currently
;; no face. So force evaluation by using `keep'.
keep)))))
(defun haskell-font-lock--forward-type (&optional ignore)
"Find where does this type declaration end.
Moves the point to the end of type declaration. It should be
invoked with point just after one of type introducing keywords
like ::, class, instance, data, newtype, type."
(interactive)
(let ((cont t)
(end (point))
(token nil)
;; we are starting right after ::
(last-token-was-operator t)
(last-token-was-newline nil)
(open-parens 0))
(while cont
(setq token (haskell-lexeme-looking-at-token 'newline))
(cond
((null token)
(setq cont nil))
((member token '(newline))
(setq last-token-was-newline (not last-token-was-operator))
(setq end (match-end 0))
(goto-char (match-end 0)))
((member (match-string-no-properties 0)
'(")" "]" "}"))
(setq open-parens (1- open-parens))
(if (< open-parens 0)
;; unmatched closing parenthesis closes type declaration
(setq cont nil)
(setq end (match-end 0))
(goto-char end))
(setq last-token-was-newline nil))
((and (member (match-string-no-properties 0)
'("," ";" "|"))
(not (member (match-string-no-properties 0) ignore)))
(if (equal 0 open-parens)
(setq cont nil)
(setq last-token-was-operator t)
(setq end (match-end 0))
(goto-char end))
(setq last-token-was-newline nil))
((and (or (member (match-string-no-properties 0)
'("<-" "=" ""))
(member (match-string-no-properties 0) haskell-font-lock-keywords))
(not (member (match-string-no-properties 0) ignore)))
(setq cont nil)
(setq last-token-was-newline nil))
((member (match-string-no-properties 0)
'("(" "[" "{"))
(if last-token-was-newline
(setq cont nil)
(setq open-parens (1+ open-parens))
(setq end (match-end 0))
(goto-char end)
(setq last-token-was-newline nil)))
((member token '(qsymid char string number template-haskell-quote template-haskell-quasi-quote))
(setq last-token-was-operator (member (haskell-lexeme-classify-by-first-char (char-after (match-beginning 1)))
'(varsym consym)))
(if (and (not last-token-was-operator) last-token-was-newline)
(setq cont nil)
(goto-char (match-end 0))
(setq end (point)))
(setq last-token-was-newline nil))
((member token '(comment nested-comment literate-comment))
(goto-char (match-end 0))
(setq end (point)))
(t
(goto-char (match-end 0))
(setq end (point))
(setq last-token-was-newline nil))))
(goto-char end)))
(defun haskell-font-lock--select-face-on-type-or-constructor ()
"Private function used to select either type or constructor face
on an uppercase identifier."
(cl-case (haskell-lexeme-classify-by-first-char (char-after (match-beginning 1)))
(varid (let ((word (match-string-no-properties 0)))
(cond
((member word haskell-font-lock-keywords)
;; Note: keywords parse as keywords only when not qualified.
;; GHC parses Control.let as a single but illegal lexeme.
(when (member word '("class" "instance" "type" "data" "newtype"))
(save-excursion
(goto-char (match-end 0))
(save-match-data
(haskell-font-lock--forward-type
(cond
((member word '("class" "instance"))
'("|"))
((member word '("type"))
;; Need to support 'type instance'
'("=" "instance")))))
(add-text-properties (match-end 0) (point) '(font-lock-multiline t haskell-type t))))
'haskell-keyword-face)
((member word '("forall"))
(when (get-text-property (match-beginning 0) 'haskell-type)
'haskell-keyword-face)))))
(conid (if (get-text-property (match-beginning 0) 'haskell-type)
'haskell-type-face
'haskell-constructor-face))
(varsym (unless (and (member (match-string 0) '("-" "+" "."))
(equal (string-to-syntax "w") (syntax-after (match-beginning 0))))
;; We need to protect against the case of
;; plus, minus or dot inside a floating
;; point number.
'haskell-operator-face))
(consym (if (not (member (match-string 1) '("::" "")))
(if (get-text-property (match-beginning 0) 'haskell-type)
'haskell-type-face
'haskell-constructor-face)
(save-excursion
(goto-char (match-end 0))
(save-match-data
(haskell-font-lock--forward-type))
(add-text-properties (match-end 0) (point) '(font-lock-multiline t haskell-type t)))
'haskell-operator-face))))
(defun haskell-font-lock--put-face-on-type-or-constructor ()
"Private function used to put either type or constructor face
on an uppercase identifier."
(let ((face (haskell-font-lock--select-face-on-type-or-constructor)))
(when (and face
(not (text-property-not-all (match-beginning 0) (match-end 0) 'face nil)))
(put-text-property (match-beginning 0) (match-end 0) 'face face))))
(defun haskell-font-lock-keywords ()
;; this has to be a function because it depends on global value of
;; `haskell-font-lock-symbols'
"Generate font lock eywords."
(let* (;; Bird-style literate scripts start a line of code with
;; "^>", otherwise a line of code starts with "^".
(line-prefix "^\\(?:> ?\\)?")
(varid "[[:lower:]_][[:alnum:]'_]*")
;; We allow ' preceding conids because of DataKinds/PolyKinds
(conid "'?[[:upper:]][[:alnum:]'_]*")
(sym "\\s.+")
;; Top-level declarations
(topdecl-var
(concat line-prefix "\\(" varid "\\(?:\\s-*,\\s-*" varid "\\)*" "\\)"
;; optionally allow for a single newline after identifier
"\\(\\s-+\\|\\s-*[\n]\\s-+\\)"
;; A toplevel declaration can be followed by a definition
;; (=), a type (::) or (∷), a guard, or a pattern which can
;; either be a variable, a constructor, a parenthesized
;; thingy, or an integer or a string.
"\\(" varid "\\|" conid "\\|::\\|∷\\|=\\||\\|\\s(\\|[0-9\"']\\)"))
(topdecl-var2
(concat line-prefix "\\(" varid "\\|" conid "\\)\\s-*`\\(" varid "\\)`"))
(topdecl-bangpat
(concat line-prefix "\\(" varid "\\)\\s-*!"))
(topdecl-sym
(concat line-prefix "\\(" varid "\\|" conid "\\)\\s-*\\(" sym "\\)"))
(topdecl-sym2 (concat line-prefix "(\\(" sym "\\))"))
keywords)
(setq keywords
`(;; NOTICE the ordering below is significant
;;
("^#\\(?:[^\\\n]\\|\\\\\\(?:.\\|\n\\|\\'\\)\\)*\\(?:\n\\|\\'\\)" 0 'font-lock-preprocessor-face t)
,@(haskell-font-lock-symbols-keywords)
;; Special case for `as', `hiding', `safe' and `qualified', which are
;; keywords in import statements but are not otherwise reserved.
("\\<import[ \t]+\\(?:\\(safe\\>\\)[ \t]*\\)?\\(?:\\(qualified\\>\\)[ \t]*\\)?\\(?:\"[^\"]*\"[\t ]*\\)?[^ \t\n()]+[ \t]*\\(?:\\(\\<as\\>\\)[ \t]*[^ \t\n()]+[ \t]*\\)?\\(\\<hiding\\>\\)?"
(1 'haskell-keyword-face nil lax)
(2 'haskell-keyword-face nil lax)
(3 'haskell-keyword-face nil lax)
(4 'haskell-keyword-face nil lax))
;; Special case for `foreign import'
;; keywords in foreign import statements but are not otherwise reserved.
("\\<\\(foreign\\)[ \t]+\\(import\\)[ \t]+\\(?:\\(ccall\\|stdcall\\|cplusplus\\|jvm\\|dotnet\\)[ \t]+\\)?\\(?:\\(safe\\|unsafe\\|interruptible\\)[ \t]+\\)?"
(1 'haskell-keyword-face nil lax)
(2 'haskell-keyword-face nil lax)
(3 'haskell-keyword-face nil lax)
(4 'haskell-keyword-face nil lax))
;; Special case for `foreign export'
;; keywords in foreign export statements but are not otherwise reserved.
("\\<\\(foreign\\)[ \t]+\\(export\\)[ \t]+\\(?:\\(ccall\\|stdcall\\|cplusplus\\|jvm\\|dotnet\\)[ \t]+\\)?"
(1 'haskell-keyword-face nil lax)
(2 'haskell-keyword-face nil lax)
(3 'haskell-keyword-face nil lax))
;; Special case for `type family' and `data family'.
;; `family' is only reserved in these contexts.
("\\<\\(type\\|data\\)[ \t]+\\(family\\>\\)"
(1 'haskell-keyword-face nil lax)
(2 'haskell-keyword-face nil lax))
;; Special case for `type role'
;; `role' is only reserved in this context.
("\\<\\(type\\)[ \t]+\\(role\\>\\)"
(1 'haskell-keyword-face nil lax)
(2 'haskell-keyword-face nil lax))
;; Toplevel Declarations.
;; Place them *before* generic id-and-op highlighting.
(,topdecl-var (1 (unless (member (match-string 1) haskell-font-lock-keywords)
'haskell-definition-face)))
(,topdecl-var2 (2 (unless (member (match-string 2) haskell-font-lock-keywords)
'haskell-definition-face)))
(,topdecl-bangpat (1 (unless (member (match-string 1) haskell-font-lock-keywords)
'haskell-definition-face)))
(,topdecl-sym (2 (unless (member (match-string 2) '("\\" "=" "->" "" "<-" "" "::" "" "," ";" "`"))
'haskell-definition-face)))
(,topdecl-sym2 (1 (unless (member (match-string 1) '("\\" "=" "->" "" "<-" "" "::" "" "," ";" "`"))
'haskell-definition-face)))
;; These four are debatable...
("(\\(,*\\|->\\))" 0 'haskell-constructor-face)
("\\[\\]" 0 'haskell-constructor-face)
("`"
(0 (if (or (elt (syntax-ppss) 3) (elt (syntax-ppss) 4))
(parse-partial-sexp (point) (point-max) nil nil (syntax-ppss)
'syntax-table)
(when (save-excursion
(goto-char (match-beginning 0))
(haskell-lexeme-looking-at-backtick))
(goto-char (match-end 0))
(unless (text-property-not-all (match-beginning 1) (match-end 1) 'face nil)
(put-text-property (match-beginning 1) (match-end 1) 'face 'haskell-operator-face))
(unless (text-property-not-all (match-beginning 2) (match-end 2) 'face nil)
(put-text-property (match-beginning 2) (match-end 2) 'face 'haskell-operator-face))
(unless (text-property-not-all (match-beginning 4) (match-end 4) 'face nil)
(put-text-property (match-beginning 4) (match-end 4) 'face 'haskell-operator-face))
(add-text-properties
(match-beginning 0) (match-end 0)
'(font-lock-fontified t fontified t font-lock-multiline t))))))
(,haskell-lexeme-idsym-first-char
(0 (if (or (elt (syntax-ppss) 3) (elt (syntax-ppss) 4))
(parse-partial-sexp (point) (point-max) nil nil (syntax-ppss)
'syntax-table)
(when (save-excursion
(goto-char (match-beginning 0))
(haskell-lexeme-looking-at-qidsym))
(goto-char (match-end 0))
;; note that we have to put face ourselves here because font-lock
;; will use match data from the original matcher
(haskell-font-lock--put-face-on-type-or-constructor)))))))
keywords))
(defun haskell-font-lock-fontify-block (lang-mode start end)
"Fontify a block as LANG-MODE."
(let ((string (buffer-substring-no-properties start end))
(modified (buffer-modified-p))
(org-buffer (current-buffer)) pos next)
(remove-text-properties start end '(face nil))
(with-current-buffer
(get-buffer-create
(concat " haskell-font-lock-fontify-block:" (symbol-name lang-mode)))
(delete-region (point-min) (point-max))
(insert string " ") ;; so there's a final property change
(cl-letf (((symbol-function 'message)
(lambda (_fmt &rest _args))))
;; silence messages
(unless (eq major-mode lang-mode) (funcall lang-mode))
(font-lock-ensure))
(setq pos (point-min))
(while (setq next (next-single-property-change pos 'face))
(put-text-property
(+ start (1- pos)) (1- (+ start next)) 'face
(or (get-text-property pos 'face) 'default) org-buffer)
(setq pos next))
(unless (equal pos (point-max))
(put-text-property
(+ start (1- pos)) (1- (+ start (point-max))) 'face
'default org-buffer)))
(add-text-properties
start end
'(font-lock-fontified t fontified t font-lock-multiline t))
(set-buffer-modified-p modified)))
(defun haskell-syntactic-face-function (state)
"`font-lock-syntactic-face-function' for Haskell."
(cond
((nth 3 state)
(if (equal ?| (nth 3 state))
;; find out what kind of QuasiQuote is this
(let* ((qqname (save-excursion
(goto-char (nth 8 state))
(skip-syntax-backward "w._")
(buffer-substring-no-properties (point) (nth 8 state))))
(lang-mode (cdr (assoc qqname haskell-font-lock-quasi-quote-modes))))
(if (and lang-mode
(fboundp lang-mode))
(save-excursion
;; find the end of the QuasiQuote
(parse-partial-sexp (point) (point-max) nil nil state
'syntax-table)
(haskell-font-lock-fontify-block lang-mode (1+ (nth 8 state)) (1- (point)))
;; must return nil here so that it is not fontified again as string
nil)
;; fontify normally as string because lang-mode is not present
'haskell-quasi-quote-face))
(save-excursion
(let
((state2
(parse-partial-sexp (point) (point-max) nil nil state
'syntax-table))
(end-of-string (point)))
(put-text-property (nth 8 state) (point)
'face 'font-lock-string-face)
(if (or (equal t (nth 3 state)) (nth 3 state2))
;; This is an unterminated string constant, use warning
;; face for the opening quote.
(put-text-property (nth 8 state) (1+ (nth 8 state))
'face 'font-lock-warning-face))
(goto-char (1+ (nth 8 state)))
(while (re-search-forward "\\\\" end-of-string t)
(goto-char (1- (point)))
(if (looking-at haskell-lexeme-string-literal-inside-item)
(goto-char (match-end 0))
;; We are looking at an unacceptable escape
;; sequence. Use warning face to highlight that.
(put-text-property (point) (1+ (point))
'face 'font-lock-warning-face)
(goto-char (1+ (point)))))))
;; must return nil here so that it is not fontified again as string
nil))
;; Detect literate comment lines starting with syntax class '<'
((save-excursion
(goto-char (nth 8 state))
(equal (string-to-syntax "<") (syntax-after (point))))
'haskell-literate-comment-face)
;; Detect pragmas. A pragma is enclosed in special comment
;; delimiters {-# .. #-}.
((save-excursion
(goto-char (nth 8 state))
(and (looking-at-p "{-#")
(forward-comment 1)
(goto-char (- (point) 3))
(looking-at-p "#-}")))
'haskell-pragma-face)
;; Detect Liquid Haskell annotations enclosed in special comment
;; delimiters {-@ .. @-}.
((save-excursion
(goto-char (nth 8 state))
(and (looking-at-p "{-@")
(forward-comment 1)
(goto-char (- (point) 3))
(looking-at-p "@-}")))
'haskell-liquid-haskell-annotation-face)
;; Haddock comment start with either "-- [|^*$]" or "{- ?[|^*$]"
;; (note space optional for nested comments and mandatory for
;; double dash comments).
;;
;; Haddock comment will also continue on next line, provided:
;; - current line is a double dash haddock comment
;; - next line is also double dash comment
;; - there is only whitespace between
;;
;; We recognize double dash haddock comments by property
;; 'font-lock-doc-face attached to newline. In case of {- -}
;; comments newline is outside of comment.
((save-excursion
(goto-char (nth 8 state))
(or (looking-at-p "\\(?:{- ?\\|-- \\)[|^*$]")
(and (looking-at-p "--") ; are we at double dash comment
(forward-line -1) ; this is nil on first line
(eq (get-text-property (line-end-position) 'face)
'font-lock-doc-face) ; is a doc face
(forward-line)
(skip-syntax-forward "-") ; see if there is only whitespace
(eq (point) (nth 8 state))))) ; we are back in position
;; Here we look inside the comment to see if there are substrings
;; worth marking inside we try to emulate as much of haddock as
;; possible. First we add comment face all over the comment, then
;; we add special features.
(let ((beg (nth 8 state))
(end (save-excursion
(parse-partial-sexp (point) (point-max) nil nil state
'syntax-table)
(point)))
(emphasis-open-point nil)
(strong-open-point nil))
(put-text-property beg end 'face 'font-lock-doc-face)
(when (fboundp 'add-face-text-property)
;; `add-face-text-property' is not defined in Emacs 23
;; iterate over chars, take escaped chars unconditionally
;; mark when a construct is opened, close and face it when
;; it is closed
(save-excursion
(while (< (point) end)
(if (looking-at "__\\|\\\\.\\|\\\n\\|[/]")
(progn
(cond
((equal (match-string 0) "/")
(if emphasis-open-point
(progn
(add-face-text-property emphasis-open-point (match-end 0)
'(:slant italic))
(setq emphasis-open-point nil))
(setq emphasis-open-point (point))))
((equal (match-string 0) "__")
(if strong-open-point
(progn
(add-face-text-property strong-open-point (match-end 0)
'(:weight bold))
(setq strong-open-point nil))
(setq strong-open-point (point))))
(t
;; this is a backslash escape sequence, skip over it
))
(goto-char (match-end 0)))
;; skip chars that are not interesting
(goto-char (1+ (point)))
(skip-chars-forward "^_\\\\/" end))))))
nil)
(t 'font-lock-comment-face)))
(defun haskell-font-lock-defaults-create ()
"Locally set `font-lock-defaults' for Haskell."
(setq-local font-lock-defaults
'((haskell-font-lock-keywords)
nil nil nil nil
(font-lock-syntactic-face-function
. haskell-syntactic-face-function)
;; Get help from font-lock-syntactic-keywords.
(parse-sexp-lookup-properties . t)
(font-lock-extra-managed-props . (composition)))))
(defun haskell-fontify-as-mode (text mode)
"Fontify TEXT as MODE, returning the fontified text."
(with-temp-buffer
(funcall mode)
(insert text)
(if (fboundp 'font-lock-ensure)
(font-lock-ensure)
(with-no-warnings (font-lock-fontify-buffer)))
(buffer-substring (point-min) (point-max))))
;; Provide ourselves:
(provide 'haskell-font-lock)
;; Local Variables:
;; coding: utf-8-unix
;; tab-width: 8
;; End:
;;; haskell-font-lock.el ends here

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -0,0 +1,151 @@
;;; haskell-hoogle.el --- Look up Haskell documentation via hoogle or hayoo -*- lexical-binding: t; -*-
;; Copyright © 2015 Steve Purcell
;; 2016 Arthur Fayzrakhmanov
;; Author: Steve Purcell <steve@sanityinc.com>
;; Keywords: docs
;; This program 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 of the License, or
;; (at your option) any later version.
;; This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; Functions for looking up documentation with hayoo or hoogle, via
;; either local or remote servers.
;;; Code:
(require 'ansi-color)
(require 'haskell-mode)
(require 'haskell-utils)
(defcustom haskell-hoogle-command
(if (executable-find "hoogle") "hoogle")
"Name of the command to use to query Hoogle.
If nil, use the Hoogle web-site."
:group 'haskell
:type '(choice (const :tag "Use Web-site" nil)
string))
(defcustom haskell-hoogle-url "http://haskell.org/hoogle/?q=%s"
"Default value for hoogle web site."
:group 'haskell
:type '(choice
(const :tag "haskell-org" "http://haskell.org/hoogle/?q=%s")
(const :tag "fp-complete" "https://www.stackage.org/lts/hoogle?q=%s")
(const :tag "hayoo" "http://hayoo.fh-wedel.de/?query=%s")
string))
;;;###autoload
(defun haskell-hoogle (query &optional info)
"Do a Hoogle search for QUERY.
When `haskell-hoogle-command' is non-nil, this command runs
that. Otherwise, it opens a hoogle search result in the browser.
If prefix argument INFO is given, then `haskell-hoogle-command'
is asked to show extra info for the items matching QUERY.."
(interactive
(let ((def (haskell-ident-at-point)))
(if (and def (symbolp def)) (setq def (symbol-name def)))
(list (read-string (if def
(format "Hoogle query (default %s): " def)
"Hoogle query: ")
nil nil def)
current-prefix-arg)))
(if (null haskell-hoogle-command)
(browse-url (format haskell-hoogle-url (url-hexify-string query)))
(let ((command (concat haskell-hoogle-command
(if info " -i " "")
" --color " (shell-quote-argument query))))
(with-help-window "*hoogle*"
(with-current-buffer standard-output
(insert (shell-command-to-string command))
(ansi-color-apply-on-region (point-min) (point-max)))))))
;;;###autoload
(defalias 'hoogle 'haskell-hoogle)
(defvar haskell-hoogle-server-process-name "emacs-local-hoogle")
(defvar haskell-hoogle-server-buffer-name (format "*%s*" haskell-hoogle-server-process-name))
(defvar haskell-hoogle-port-number 49513 "Port number.")
(defvar haskell-hoogle-server-process nil "The process handle of the local hoogle server.")
(defun haskell-hoogle-start-server ()
"Start hoogle local server."
(interactive)
(if (executable-find "hoogle")
(unless (haskell-hoogle-server-live-p)
(set 'haskell-hoogle-server-process
(start-process
haskell-hoogle-server-process-name
(get-buffer-create haskell-hoogle-server-buffer-name)
"hoogle" "server" "-p" (number-to-string haskell-hoogle-port-number))))
(error "\"hoogle\" executable not found")))
(defun haskell-hoogle-server-live-p ()
"Whether the hoogle server process is live."
(condition-case _err
(process-live-p haskell-hoogle-server-process)
(error nil)))
(defun haskell-hoogle-kill-server ()
"Kill the hoogle server if it is live."
(interactive)
(when (haskell-hoogle-server-live-p)
(kill-process (get-buffer-create haskell-hoogle-server-buffer-name))
(set 'haskell-hoogle-server-process nil)))
;;;###autoload
(defun haskell-hoogle-lookup-from-local ()
"Lookup by local hoogle."
(interactive)
(if (haskell-hoogle-server-live-p)
(browse-url (format "http://localhost:%i/?hoogle=%s"
haskell-hoogle-port-number
(read-string "hoogle: " (haskell-ident-at-point))))
(haskell-mode-toggle-interactive-prompt-state)
(unwind-protect
(when (y-or-n-p "Hoogle server not running, start hoogle server? ")
(haskell-hoogle-start-server))
(haskell-mode-toggle-interactive-prompt-state t))))
(defcustom haskell-hayoo-url "http://hayoo.fh-wedel.de/?query=%s"
"Default value for hayoo web site."
:group 'haskell
:type '(choice
(const :tag "fh-wedel.de" "http://hayoo.fh-wedel.de/?query=%s")
string))
;;;###autoload
(defun haskell-hayoo (query)
"Do a Hayoo search for QUERY."
(interactive
(let ((def (haskell-ident-at-point)))
(if (and def (symbolp def)) (setq def (symbol-name def)))
(list (read-string (if def
(format "Hayoo query (default %s): " def)
"Hayoo query: ")
nil nil def))))
(browse-url (format haskell-hayoo-url (url-hexify-string query))))
;;;###autoload
(defalias 'hayoo 'haskell-hayoo)
(provide 'haskell-hoogle)
;;; haskell-hoogle.el ends here

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,513 @@
;;; haskell-lexeme.el --- haskell lexical tokens -*- coding: utf-8; lexical-binding: t -*-
;; Copyright (C) 2015 Gracjan Polak
;; 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 this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;;; Code:
(require 'rx)
(unless (category-docstring ?P)
(define-category ?P "Haskell symbol constituent characters")
(map-char-table
#'(lambda (key val)
(if (or
(and (consp key) (> (car key) 128))
(and (numberp key) (> key 128)))
(if (member val '(Pc Pd Po Sm Sc Sk So))
(modify-category-entry key ?P))))
unicode-category-table)
(dolist (key (string-to-list "!#$%&*+./<=>?@^|~\\-:"))
(modify-category-entry key ?P)))
(defconst haskell-lexeme-modid
"[[:upper:]][[:alnum:]'_]*"
"Regexp matching a valid Haskell module identifier.
Note that GHC accepts Unicode category UppercaseLetter as a first
character. Following letters are from Unicode categories
UppercaseLetter, LowercaseLetter, OtherLetter, TitlecaseLetter,
ModifierLetter, DecimalNumber, OtherNumber, backslash or
underscore.")
(defconst haskell-lexeme-id
"[[:alpha:]_][[:alnum:]'_]*"
"Regexp matching a valid Haskell identifier.
GHC accepts a string starting with any alphabetic character or
underscore followed by any alphanumeric character or underscore
or apostrophe.")
(defconst haskell-lexeme-sym
"\\cP+"
"Regexp matching a valid Haskell variable or constructor symbol.
GHC accepts a string of chars from the set
[:!#$%&*+./<=>?@^|~\\-] or Unicode category Symbol for chars with
codes larger than 128 only.")
(defconst haskell-lexeme-idsym-first-char
"\\(?:[[:alpha:]_]\\|\\cP\\)"
"Regexp matching first character of a qualified or unqualified
identifier or symbol.
Useful for `re-search-forward'.")
(defconst haskell-lexeme-modid-opt-prefix
(concat "\\(?:" haskell-lexeme-modid "\\.\\)*")
"Regexp matching a valid Haskell module prefix, potentially empty.
Module path prefix is separated by dots and finishes with a
dot. For path component syntax see `haskell-lexeme-modid'.")
(defconst haskell-lexeme-qid-or-qsym
(rx-to-string `(: (regexp ,haskell-lexeme-modid-opt-prefix)
(group (| (regexp ,haskell-lexeme-id) (regexp ,haskell-lexeme-sym)
))))
"Regexp matching a valid qualified identifier or symbol.
Note that (match-string 1) returns the unqualified part.")
(defun haskell-lexeme-looking-at-qidsym ()
"Non-nil when point is just in front of an optionally qualified
identifier or symbol.
Using this function is more efficient than matching against the
regexp `haskell-lexeme-qid-or-qsym'.
Returns:
'qid - if matched a qualified id: 'Data.Map' or 'Map'
'qsym - if matched a qualified id: 'Monad.>>=' or '>>='
'qprefix - if matched only modid prefix: 'Data.'
After successful 'qid or 'qsym match (match-string 1) will return
the unqualified part (if any)."
(let ((begin (point))
(match-data-old (match-data)))
(save-excursion
(while (looking-at (concat haskell-lexeme-modid "\\."))
(goto-char (match-end 0)))
(cond
((looking-at haskell-lexeme-id)
(let ((beg (match-beginning 0))
(end (match-end 0)))
;; check is MagicHash is present at the end of the token
(goto-char end)
(when (looking-at "#+")
(setq end (match-end 0)))
(set-match-data
(list begin end
beg end)))
'qid)
((looking-at haskell-lexeme-sym)
(set-match-data
(list begin (match-end 0)
(match-beginning 0) (match-end 0)))
'qsym)
((equal begin (point))
(set-match-data match-data-old)
nil)
(t
(set-match-data
(list begin (point)
nil nil))
'qprefix)))))
(defun haskell-lexeme-looking-at-backtick ()
"Non-nil when point is just in front of an identifier quoted with backticks.
When match is successful, match-data will contain:
(match-text 1) - opening backtick
(match-text 2) - whole qualified identifier
(match-text 3) - unqualified part of identifier
(match-text 4) - closing backtick"
(let ((match-data-old (match-data))
first-backtick-start
last-backtick-start
qid-start
id-start
id-end
result)
(save-excursion
(when (looking-at "`")
(setq first-backtick-start (match-beginning 0))
(goto-char (match-end 0))
(forward-comment (buffer-size))
(when (haskell-lexeme-looking-at-qidsym)
(setq qid-start (match-beginning 0))
(setq id-start (match-beginning 1))
(setq id-end (match-end 1))
(goto-char (match-end 0))
(forward-comment (buffer-size))
(when (looking-at "`")
(setq last-backtick-start (match-beginning 0))
(set-match-data
(mapcar
(lambda (p)
(set-marker (make-marker) p))
(list
first-backtick-start (1+ last-backtick-start)
first-backtick-start (1+ first-backtick-start)
qid-start id-end
id-start id-end
last-backtick-start (1+ last-backtick-start))))
(setq result t)))))
(unless result
(set-match-data match-data-old))
result))
(defconst haskell-lexeme-qid
(rx-to-string `(: (regexp "'*")
(regexp ,haskell-lexeme-modid-opt-prefix)
(group (regexp ,haskell-lexeme-id))))
"Regexp matching a valid qualified identifier.
Note that (match-string 1) returns the unqualified part.")
(defconst haskell-lexeme-qsym
(rx-to-string `(: (regexp "'*")
(regexp ,haskell-lexeme-modid-opt-prefix)
(group (regexp ,haskell-lexeme-id))))
"Regexp matching a valid qualified symbol.
Note that (match-string 1) returns the unqualified part.")
(defconst haskell-lexeme-number
(rx (| (: (regexp "[0-9]+\\.[0-9]+") (opt (regexp "[eE][-+]?[0-9]+")))
(regexp "[0-9]+[eE][-+]?[0-9]+")
(regexp "0[xX][0-9a-fA-F]+")
(regexp "0[oO][0-7]+")
(regexp "[0-9]+")))
"Regexp matching a floating point, decimal, octal or hexadecimal number.
Note that negative sign char is not part of a number.")
(defconst haskell-lexeme-char-literal-inside
(rx (| (not (any "\n'\\"))
(: "\\"
(| "a" "b" "f" "n" "r" "t" "v" "\\" "\"" "'"
"NUL" "SOH" "STX" "ETX" "EOT" "ENQ" "ACK"
"BEL" "BS" "HT" "LF" "VT" "FF" "CR" "SO" "SI" "DLE"
"DC1" "DC2" "DC3" "DC4" "NAK" "SYN" "ETB" "CAN"
"EM" "SUB" "ESC" "FS" "GS" "RS" "US" "SP" "DEL"
(regexp "[0-9]+")
(: "x" (regexp "[0-9a-fA-F]+"))
(: "o" (regexp "[0-7]+"))
(: "^" (regexp "[]A-Z@^_\\[]"))))))
"Regexp matching an inside of a character literal.
Note that `haskell-lexeme-char-literal-inside' matches strictly
only escape sequences defined in Haskell Report.")
(defconst haskell-lexeme--char-literal-rx
(rx-to-string `(: (group "'")
(| (: (group (regexp "[[:alpha:]_([]")) (group "'")) ; exactly one char
(: (group (| (regexp "\\\\[^\n][^'\n]*") ; allow quote just after first backslash
(regexp "[^[:alpha:]_(['\n][^'\n]*")))
(| (group "'") "\n" (regexp "\\'"))))))
"Regexp matching a character literal lookalike.
Note that `haskell-lexeme--char-literal-rx' matches more than
Haskell Report specifies because we want to support also code
under edit.
Character literals end with a quote or a newline or end of
buffer.
Regexp has subgroup expressions:
(match-text 1) matches the opening quote.
(match-text 2) matches the inside of the character literal.
(match-text 3) matches the closing quote or an empty string
at the end of line or the end buffer.")
(defun haskell-lexeme-looking-at-char-literal ()
"Non-nil when point is at a char literal lookalike.
Note that this function matches more than Haskell Report
specifies because we want to support also code under edit.
Char literals end with a quote or an unescaped newline or end
of buffer.
After successful match:
(match-text 1) matches the opening quote.
(match-text 2) matches the inside of the char literla.
(match-text 3) matches the closing quote, or a closing
newline or is nil when at the end of the buffer."
(when (looking-at haskell-lexeme--char-literal-rx)
(set-match-data
(list (match-beginning 0) (match-end 0)
(match-beginning 1) (match-end 1)
(or (match-beginning 2) (match-beginning 4)) (or (match-end 2) (match-end 4))
(or (match-beginning 3) (match-beginning 5)) (or (match-end 3) (match-end 5))))
t))
(defconst haskell-lexeme-string-literal-inside-item
(rx (| (not (any "\n\"\\"))
(: "\\"
(| "a" "b" "f" "n" "r" "t" "v" "\\" "\"" "'" "&"
"NUL" "SOH" "STX" "ETX" "EOT" "ENQ" "ACK"
"BEL" "BS" "HT" "LF" "VT" "FF" "CR" "SO" "SI" "DLE"
"DC1" "DC2" "DC3" "DC4" "NAK" "SYN" "ETB" "CAN"
"EM" "SUB" "ESC" "FS" "GS" "RS" "US" "SP" "DEL"
(regexp "[0-9]+")
(: "x" (regexp "[0-9a-fA-F]+"))
(: "o" (regexp "[0-7]+"))
(: "^" (regexp "[]A-Z@^_\\[]"))
(regexp "[ \t\n\r\v\f]*\\\\")))))
"Regexp matching an item that is a single character or a single
escape sequence inside of a string literal.
Note that `haskell-lexeme-string-literal-inside-item' matches
strictly only escape sequences defined in Haskell Report.")
(defconst haskell-lexeme-string-literal
(rx (: (group "\"")
(group (* (| (regexp "\\\\[ \t\n\r\v\f]*\\\\")
(regexp "\\\\[ \t\n\r\v\f]+")
(regexp "\\\\[^ \t\n\r\v\f]")
(* (regexp "[^\"\n\\]")))))
(group (| "\"" (regexp "$") (regexp "\\\\?\\'")
))))
"Regexp matching a string literal lookalike.
Note that `haskell-lexeme-string-literal' matches more than
Haskell Report specifies because we want to support also code
under edit.
String literals end with double quote or unescaped newline or end
of buffer.
Regexp has subgroup expressions:
(match-text 1) matches the opening double quote.
(match-text 2) matches the inside of the string.
(match-text 3) matches the closing double quote or an empty string
at the end of line or the end buffer.")
(defun haskell-lexeme-looking-at-string-literal ()
"Non-nil when point is at a string literal lookalike.
Note that this function matches more than Haskell Report
specifies because we want to support also code under edit.
String literals end with double quote or unescaped newline or end
of buffer.
After successful match:
(match-text 1) matches the opening doublequote.
(match-text 2) matches the inside of the string.
(match-text 3) matches the closing quote, or a closing
newline or is nil when at the end of the buffer."
(when (looking-at "\"")
(save-excursion
(let ((begin (point)))
(goto-char (match-end 0))
(let (finish)
(while (and (not finish)
(re-search-forward "[\"\n\\]" nil 'goto-eob))
(cond
((equal (match-string 0) "\\")
(if (looking-at "[ \t\n\r\v\f]+\\\\?")
(goto-char (match-end 0))
(goto-char (1+ (point)))))
((equal (match-string 0) "\"")
(set-match-data
(list begin (match-end 0)
begin (1+ begin)
(1+ begin) (match-beginning 0)
(match-beginning 0) (match-end 0)))
(setq finish t))
((equal (match-string 0) "\n")
(set-match-data
(list begin (match-beginning 0)
begin (1+ begin)
(1+ begin) (match-beginning 0)
nil nil))
(setq finish t))))
(unless finish
;; string closed by end of buffer
(set-match-data
(list begin (point)
begin (1+ begin)
(1+ begin) (point)
nil nil))))))
;; there was a match
t))
(defun haskell-lexeme-looking-at-quasi-quote-literal ()
"Non-nil when point is just in front of Template Haskell
quaisquote literal.
Quasi quotes start with '[xxx|' or '[$xxx|' sequence and end with
'|]'. The 'xxx' is a quoter name. There is no escaping mechanism
provided for the ending sequence.
Regexp has subgroup expressions:
(match-text 1) matches the quoter name (without $ sign if present).
(match-text 2) matches the opening vertical bar.
(match-text 3) matches the inside of the quoted string.
(match-text 4) matches the closing vertical bar
or nil if at the end of the buffer.
Note that this function excludes 'e', 't', 'd', 'p' as quoter
names according to Template Haskell specification."
(let ((match-data-old (match-data)))
(if (and
(looking-at (rx-to-string `(: "[" (optional "$")
(group (regexp ,haskell-lexeme-id))
(group "|"))))
(equal (haskell-lexeme-classify-by-first-char (char-after (match-beginning 1)))
'varid)
(not (member (match-string 1) '("e" "t" "d" "p"))))
(save-excursion
;; note that quasi quote syntax does not have any escaping
;; mechanism and if not closed it will span til lthe end of buffer
(goto-char (match-end 0))
(let ((match-data (match-data))
(match-data-2 (and (re-search-forward "|]" nil t)
(match-data))))
(if match-data-2
(set-match-data
(list
(nth 0 match-data) (nth 1 match-data-2) ;; whole match
(nth 2 match-data) (nth 3 match-data) ;; quoter name
(nth 4 match-data) (nth 5 match-data) ;; opening bar
(nth 5 match-data) (nth 0 match-data-2) ;; inner string
(nth 0 match-data-2) (1+ (nth 0 match-data-2)))) ;; closing bar
(set-match-data
(list
(nth 0 match-data) (point-max) ;; whole match
(nth 2 match-data) (nth 3 match-data) ;; quoter name
(nth 4 match-data) (nth 5 match-data) ;; opening bar
(nth 5 match-data) (point-max) ;; inner string
nil nil)) ;; closing bar
))
t)
;; restore old match data if not matched
(set-match-data match-data-old)
nil)))
(defun haskell-lexeme-classify-by-first-char (char)
"Classify token by CHAR.
CHAR is a chararacter that is assumed to be the first character
of a token."
(let ((category (get-char-code-property (or char ?\ ) 'general-category)))
(cond
((or (member char '(?! ?# ?$ ?% ?& ?* ?+ ?. ?/ ?< ?= ?> ?? ?@ ?^ ?| ?~ ?\\ ?-))
(and (> char 127)
(member category '(Pc Pd Po Sm Sc Sk So))))
'varsym)
((equal char ?:)
'consym)
((equal char ?\')
'char)
((equal char ?\")
'string)
((member category '(Lu Lt))
'conid)
((or (equal char ?_)
(member category '(Ll Lo)))
'varid)
((and (>= char ?0) (<= char ?9))
'number)
((member char '(?\] ?\[ ?\( ?\) ?\{ ?\} ?\` ?\, ?\;))
'special))))
(defun haskell-lexeme-looking-at-token (&rest flags)
"Like `looking-at' but understands Haskell lexemes.
Moves point forward over whitespace. Returns a symbol describing
type of Haskell token recognized. Use `match-string',
`match-beginning' and `match-end' with argument 0 to query match
result.
Possible results are:
- 'special: for chars [](){}`,;
- 'comment: for single line comments
- 'nested-comment: for multiline comments
- 'qsymid: for qualified identifiers or symbols
- 'string: for strings literals
- 'char: for char literals
- 'number: for decimal, float, hexadecimal and octal number literals
- 'template-haskell-quote: for a string of apostrophes for template haskell
- 'template-haskell-quasi-quote: for a string of apostrophes for template haskell
Note that for qualified symbols (match-string 1) returns the
unqualified identifier or symbol. Further qualification for
symbol or identifier can be done with:
(haskell-lexeme-classify-by-first-char (char-after (match-beginning 1)))
See `haskell-lexeme-classify-by-first-char' for details."
(while
;; Due to how unterminated strings terminate at newline, some
;; newlines have syntax set to generic string delimeter. We want
;; those to be treated as whitespace anyway
(or
(> (skip-syntax-forward "-") 0)
(and (not (member 'newline flags))
(> (skip-chars-forward "\n") 0))))
(let
((case-fold-search nil)
(point (point-marker)))
(or
(and
(equal (string-to-syntax "<")
(get-char-property (point) 'syntax-table))
(progn
(set-match-data (list point (set-marker (make-marker) (line-end-position))))
'literate-comment))
(and (looking-at "\n")
'newline)
(and (looking-at "{-")
(save-excursion
(forward-comment 1)
(set-match-data (list point (point-marker)))
'nested-comment))
(and (haskell-lexeme-looking-at-char-literal)
'char)
(and (haskell-lexeme-looking-at-string-literal)
'string)
(and (looking-at "[][(){}`,;]")
(if (haskell-lexeme-looking-at-quasi-quote-literal)
'template-haskell-quasi-quote
'special))
(and (haskell-lexeme-looking-at-qidsym)
(if (save-match-data
(string-match "\\`---*\\'" (match-string-no-properties 0)))
(progn
(set-match-data (list point (set-marker (make-marker) (line-end-position))))
'comment)
'qsymid))
(and (looking-at haskell-lexeme-number)
'number)
(and (looking-at "'+")
'template-haskell-quote)
(and (looking-at ".")
'illegal))))
(provide 'haskell-lexeme)
;;; haskell-lexeme.el ends here

Binary file not shown.

View File

@@ -0,0 +1,632 @@
;;; haskell-load.el --- Compiling and loading modules in the GHCi process -*- lexical-binding: t -*-
;; Copyright © 2014 Chris Done. All rights reserved.
;; 2016 Arthur Fayzrakhmanov
;; 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 this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;;; Code:
(require 'cl-lib)
(require 'haskell-mode)
(require 'haskell-process)
(require 'haskell-interactive-mode)
(require 'haskell-modules)
(require 'haskell-commands)
(require 'haskell-session)
(require 'haskell-string)
(defun haskell-process-look-config-changes (session)
"Check whether a cabal configuration file has changed.
Restarts the SESSION's process if that is the case."
(let ((current-checksum (haskell-session-get session 'cabal-checksum))
(new-checksum (haskell-cabal-compute-checksum
(haskell-session-get session 'cabal-dir))))
(when (not (string= current-checksum new-checksum))
(haskell-interactive-mode-echo
session
(format "Cabal file changed: %s" new-checksum))
(haskell-session-set-cabal-checksum
session
(haskell-session-get session 'cabal-dir))
(haskell-mode-toggle-interactive-prompt-state)
(unwind-protect
(unless
(and haskell-process-prompt-restart-on-cabal-change
(not
(y-or-n-p "Cabal file changed. Restart GHCi process? ")))
(haskell-process-start (haskell-interactive-session)))
(haskell-mode-toggle-interactive-prompt-state t)))))
(defun haskell-process-live-build (process buffer echo-in-repl)
"Show live updates for loading files."
(cond
((haskell-process-consume
process
(concat "\\[[ ]*\\([0-9]+\\) of \\([0-9]+\\)\\]"
" Compiling \\([^ ]+\\)[ ]+"
"( \\([^ ]+\\), \\([^ ]+\\) )[^\r\n]*[\r\n]+"))
(haskell-process-echo-load-message process buffer echo-in-repl nil)
t)
((haskell-process-consume
process
(concat "\\[[ ]*\\([0-9]+\\) of \\([0-9]+\\)\\]"
" Compiling \\[TH\\] \\([^ ]+\\)[ ]+"
"( \\([^ ]+\\), \\([^ ]+\\) )[^\r\n]*[\r\n]+"))
(haskell-process-echo-load-message process buffer echo-in-repl t)
t)
((haskell-process-consume
process
"Loading package \\([^ ]+\\) ... linking ... done.\n")
(haskell-mode-message-line
(format "Loading: %s"
(match-string 1 buffer)))
t)
((haskell-process-consume
process
"^Preprocessing executables for \\(.+?\\)\\.\\.\\.")
(let ((msg (format "Preprocessing: %s" (match-string 1 buffer))))
(haskell-interactive-mode-echo (haskell-process-session process) msg)
(haskell-mode-message-line msg)))
((haskell-process-consume process "Linking \\(.+?\\) \\.\\.\\.")
(let ((msg (format "Linking: %s" (match-string 1 buffer))))
(haskell-interactive-mode-echo (haskell-process-session process) msg)
(haskell-mode-message-line msg)))
((haskell-process-consume process "\nBuilding \\(.+?\\)\\.\\.\\.")
(let ((msg (format "Building: %s" (match-string 1 buffer))))
(haskell-interactive-mode-echo (haskell-process-session process) msg)
(haskell-mode-message-line msg)))
((string-match "Collecting type info for [[:digit:]]+ module(s) \\.\\.\\."
(haskell-process-response process)
(haskell-process-response-cursor process))
(haskell-mode-message-line (match-string 0 buffer))
;; Do not consume "Ok, modules loaded" that goes before
;; "Collecting type info...", just exit.
nil)))
(defun haskell-process-load-complete (session process buffer reload module-buffer &optional cont)
"Handle the complete loading response. BUFFER is the string of
text being sent over the process pipe. MODULE-BUFFER is the
actual Emacs buffer of the module being loaded."
(when (get-buffer (format "*%s:splices*" (haskell-session-name session)))
(with-current-buffer (haskell-interactive-mode-splices-buffer session)
(erase-buffer)))
(let* ((ok (cond
((haskell-process-consume
process
"Ok, \\(?:[0-9]+\\) modules? loaded\\.$")
t)
((haskell-process-consume
process
"Failed, \\(?:[0-9]+\\) modules? loaded\\.$")
nil)
((haskell-process-consume
process
"Ok, modules loaded: \\(.+\\)\\.$")
t)
((haskell-process-consume
process
"Failed, modules loaded: \\(.+\\)\\.$")
nil)
(t
(error (message "Unexpected response from haskell process.")))))
(modules (haskell-process-extract-modules buffer))
(cursor (haskell-process-response-cursor process))
(warning-count 0))
(haskell-process-set-response-cursor process 0)
(haskell-check-remove-overlays module-buffer)
(while
(haskell-process-errors-warnings module-buffer session process buffer)
(setq warning-count (1+ warning-count)))
(haskell-process-set-response-cursor process cursor)
(if (and (not reload)
haskell-process-reload-with-fbytecode)
(haskell-process-reload-with-fbytecode process module-buffer)
(haskell-process-import-modules process (car modules)))
(if ok
(haskell-mode-message-line (if reload "Reloaded OK." "OK."))
(haskell-interactive-mode-compile-error session "Compilation failed."))
(when cont
(condition-case-unless-debug e
(funcall cont ok)
(error (message "%S" e))
(quit nil)))))
(defun haskell-process-suggest-imports (session file modules ident)
"Suggest add missed imports to file.
Asks user to add to SESSION's FILE missed import. MODULES is a
list of modules where missed IDENT was found."
(cl-assert session)
(cl-assert file)
(cl-assert ident)
(haskell-mode-toggle-interactive-prompt-state)
(unwind-protect
(let* ((process (haskell-session-process session))
(suggested-already (haskell-process-suggested-imports process))
(module
(cond
((> (length modules) 1)
(when (y-or-n-p
(format
"Identifier `%s' not in scope, choose module to import?"
ident))
(haskell-complete-module-read "Module: " modules)))
((= (length modules) 1)
(let ((module (car modules)))
(unless (member module suggested-already)
(haskell-process-set-suggested-imports
process
(cons module suggested-already))
(when (y-or-n-p
(format "Identifier `%s' not in scope, import `%s'?"
ident
module))
module)))))))
(when module
(haskell-process-find-file session file)
(haskell-add-import module)))
(haskell-mode-toggle-interactive-prompt-state t)))
(defun haskell-process-trigger-suggestions (session msg file line)
"Trigger prompting to add any extension suggestions."
(cond ((let ((case-fold-search nil))
(or
(and (string-match " -X\\([A-Z][A-Za-z]+\\)" msg)
(not (string-match "\\([A-Z][A-Za-z]+\\) is deprecated" msg)))
(string-match "Use \\([A-Z][A-Za-z]+\\) to permit this" msg)
(string-match "Use \\([A-Z][A-Za-z]+\\) to allow" msg)
(string-match "Use \\([A-Z][A-Za-z]+\\) to enable" msg)
(string-match
"Use \\([A-Z][A-Za-z]+\\) if you want to disable this"
msg)
(string-match "use \\([A-Z][A-Za-z]+\\)" msg)
(string-match "You need \\([A-Z][A-Za-z]+\\)" msg)))
(when haskell-process-suggest-language-pragmas
(haskell-process-suggest-pragma
session
"LANGUAGE"
(match-string 1 msg)
file)))
((string-match
" The \\(qualified \\)?import of[ ][`]\\([^ ]+\\)['] is redundant"
msg)
(when haskell-process-suggest-remove-import-lines
(haskell-process-suggest-remove-import
session
file
(match-string 2 msg)
line)))
((string-match "[Ww]arning: orphan instance: " msg)
(when haskell-process-suggest-no-warn-orphans
(haskell-process-suggest-pragma
session
"OPTIONS" "-fno-warn-orphans"
file)))
((or (string-match "against inferred type [`]\\[Char\\][']" msg)
(string-match "with actual type [`]\\[Char\\][']" msg))
(when haskell-process-suggest-overloaded-strings
(haskell-process-suggest-pragma
session
"LANGUAGE" "OverloadedStrings"
file)))
((string-match "^Not in scope: .*[`]\\(.+\\)[']$" msg)
(let* ((match1 (match-string 1 msg))
(ident (if (string-match "^[A-Za-z0-9_'.]+\\.\\(.+\\)$" match1)
;; Skip qualification.
(match-string 1 match1)
match1)))
(when haskell-process-suggest-hoogle-imports
(let ((modules (haskell-process-hoogle-ident ident)))
(haskell-process-suggest-imports session file modules ident)))
(when haskell-process-suggest-haskell-docs-imports
(let ((modules (haskell-process-haskell-docs-ident ident)))
(haskell-process-suggest-imports session file modules ident)))
(when haskell-process-suggest-hayoo-imports
(let ((modules (haskell-process-hayoo-ident ident)))
(haskell-process-suggest-imports session file modules ident)))))
((string-match "^[ ]+It is a member of the hidden package [`]\\([^@\r\n]+\\).*['].$" msg)
(when haskell-process-suggest-add-package
(haskell-process-suggest-add-package session msg)))))
(defun haskell-process-do-cabal (command)
"Run a Cabal command."
(let ((process (ignore-errors
(haskell-interactive-process))))
(cond
((or (eq process nil)
(let ((child (haskell-process-process process)))
(not (equal 'run (process-status child)))))
(message "Process is not running, so running directly.")
(shell-command (concat "cabal " command)
(get-buffer-create "*haskell-process-log*")
(get-buffer-create "*haskell-process-log*"))
(switch-to-buffer-other-window (get-buffer "*haskell-process-log*")))
(t (haskell-process-queue-command
process
(make-haskell-command
:state (list (haskell-interactive-session) process command 0)
:go
(lambda (state)
(haskell-process-send-string
(cadr state)
(format haskell-process-do-cabal-format-string
(haskell-session-cabal-dir (car state))
(format "%s %s"
(cl-ecase (haskell-process-type)
('ghci haskell-process-path-cabal)
('cabal-repl haskell-process-path-cabal)
('cabal-new-repl haskell-process-path-cabal)
('cabal-ghci haskell-process-path-cabal)
('stack-ghci haskell-process-path-stack))
(cl-caddr state)))))
:live
(lambda (state buffer)
(let ((cmd (replace-regexp-in-string "^\\([a-z]+\\).*"
"\\1"
(cl-caddr state))))
(cond ((or (string= cmd "build")
(string= cmd "install"))
(haskell-process-live-build (cadr state) buffer t))
(t
(haskell-process-cabal-live state buffer)))))
:complete
(lambda (state response)
(let* ((process (cadr state))
(session (haskell-process-session process))
(message-count 0)
(cursor (haskell-process-response-cursor process)))
;; XXX: what the hell about the rampant code duplication?
(haskell-process-set-response-cursor process 0)
(while (haskell-process-errors-warnings nil session process response)
(setq message-count (1+ message-count)))
(haskell-process-set-response-cursor process cursor)
(let ((msg (format "Complete: cabal %s (%s compiler messages)"
(cl-caddr state)
message-count)))
(haskell-interactive-mode-echo session msg)
(when (= message-count 0)
(haskell-interactive-mode-echo
session
"No compiler messages, dumping complete output:")
(haskell-interactive-mode-echo session response))
(haskell-mode-message-line msg)
(when (and haskell-notify-p
(fboundp 'notifications-notify))
(notifications-notify
:title (format "*%s*" (haskell-session-name (car state)))
:body msg
:app-name (cl-ecase (haskell-process-type)
('ghci haskell-process-path-cabal)
('cabal-repl haskell-process-path-cabal)
('cabal-new-repl haskell-process-path-cabal)
('cabal-ghci haskell-process-path-cabal)
('stack-ghci haskell-process-path-stack))
:app-icon haskell-process-logo)))))))))))
(defun haskell-process-echo-load-message (process buffer echo-in-repl th)
"Echo a load message."
(let ((session (haskell-process-session process))
(module-name (match-string 3 buffer))
(file-name (match-string 4 buffer)))
(haskell-interactive-show-load-message
session
'compiling
module-name
(haskell-session-strip-dir session file-name)
echo-in-repl
th)))
(defun haskell-process-extract-modules (buffer)
"Extract the modules from the process buffer."
(let* ((modules-string (match-string 1 buffer))
(modules (split-string modules-string ", ")))
(cons modules modules-string)))
;;;###autoload
(defface haskell-error-face
'((((supports :underline (:style wave)))
:underline (:style wave :color "#dc322f"))
(t
:inherit error))
"Face used for marking error lines."
:group 'haskell-mode)
;;;###autoload
(defface haskell-warning-face
'((((supports :underline (:style wave)))
:underline (:style wave :color "#b58900"))
(t
:inherit warning))
"Face used for marking warning lines."
:group 'haskell-mode)
;;;###autoload
(defface haskell-hole-face
'((((supports :underline (:style wave)))
:underline (:style wave :color "#6c71c4"))
(t
:inherit warning))
"Face used for marking hole lines."
:group 'haskell-mode)
(defvar haskell-check-error-fringe (propertize "!" 'display '(left-fringe exclamation-mark)))
(defvar haskell-check-warning-fringe (propertize "?" 'display '(left-fringe question-mark)))
(defvar haskell-check-hole-fringe (propertize "_" 'display '(left-fringe horizontal-bar)))
(defun haskell-check-overlay-p (ovl)
(overlay-get ovl 'haskell-check))
(defun haskell-check-filter-overlays (xs)
(cl-remove-if-not 'haskell-check-overlay-p xs))
(defun haskell-check-remove-overlays (buffer)
(with-current-buffer buffer
(remove-overlays (point-min) (point-max) 'haskell-check t)))
(defmacro haskell-with-overlay-properties (proplist ovl &rest body)
"Evaluate BODY with names in PROPLIST bound to the values of
correspondingly-named overlay properties of OVL."
(let ((ovlvar (cl-gensym "OVL-")))
`(let* ((,ovlvar ,ovl)
,@(mapcar (lambda (p) `(,p (overlay-get ,ovlvar ',p))) proplist))
,@body)))
(defun haskell-overlay-start> (o1 o2)
(> (overlay-start o1) (overlay-start o2)))
(defun haskell-overlay-start< (o1 o2)
(< (overlay-start o1) (overlay-start o2)))
(defun haskell-first-overlay-in-if (test beg end)
(let ((ovls (cl-remove-if-not test (overlays-in beg end))))
(cl-first (sort (cl-copy-list ovls) 'haskell-overlay-start<))))
(defun haskell-last-overlay-in-if (test beg end)
(let ((ovls (cl-remove-if-not test (overlays-in beg end))))
(cl-first (sort (cl-copy-list ovls) 'haskell-overlay-start>))))
(defun haskell-error-overlay-briefly (ovl)
(haskell-with-overlay-properties
(haskell-msg haskell-msg-type) ovl
(cond
((not (eq haskell-msg-type 'warning))
haskell-msg)
((string-prefix-p "[Ww]arning:\n " haskell-msg)
(cl-subseq haskell-msg 13))
(t
(error
"Invariant failed: a warning message from GHC has unexpected form: %s."
haskell-msg)))))
(defun haskell-goto-error-overlay (ovl)
(cond (ovl
(goto-char (overlay-start ovl))
(haskell-mode-message-line (haskell-error-overlay-briefly ovl)))
(t
(message "No further notes from Haskell compiler."))))
(defun haskell-goto-first-error ()
(interactive)
(haskell-goto-error-overlay
(haskell-first-overlay-in-if 'haskell-check-overlay-p
(buffer-end 0) (buffer-end 1))))
(defun haskell-goto-prev-error ()
(interactive)
(haskell-goto-error-overlay
(let ((ovl-at
(cl-first (haskell-check-filter-overlays (overlays-at (point))))))
(or (haskell-last-overlay-in-if 'haskell-check-overlay-p
(point-min)
(if ovl-at (overlay-start ovl-at) (point)))
ovl-at))))
(defun haskell-goto-next-error ()
(interactive)
(haskell-goto-error-overlay
(let ((ovl-at
(cl-first (haskell-check-filter-overlays (overlays-at (point))))))
(or (haskell-first-overlay-in-if
'haskell-check-overlay-p
(if ovl-at (overlay-end ovl-at) (point)) (point-max))
ovl-at))))
(defun haskell-check-paint-overlay
(buffer error-from-this-file-p line msg file type hole coln)
(with-current-buffer buffer
(let (beg end)
(goto-char (point-min))
;; XXX: we can avoid excess buffer walking by relying on the maybe-fact
;; that GHC sorts error messages by line number, maybe.
(cond
(error-from-this-file-p
(forward-line (1- line))
(forward-char (1- coln))
(setq beg (point))
(if (eq type 'hole)
(forward-char (length hole))
(skip-chars-forward "^[:space:]" (line-end-position)))
(setq end (point)))
(t
(setq beg (point))
(forward-line)
(setq end (point))))
(let ((ovl (make-overlay beg end)))
(overlay-put ovl 'haskell-check t)
(overlay-put ovl 'haskell-file file)
(overlay-put ovl 'haskell-msg msg)
(overlay-put ovl 'haskell-msg-type type)
(overlay-put ovl 'help-echo msg)
(overlay-put ovl 'haskell-hole hole)
(cl-destructuring-bind
(face fringe)
(cl-case type
(warning
(list 'haskell-warning-face haskell-check-warning-fringe))
(hole
(list 'haskell-hole-face haskell-check-hole-fringe))
(error
(list 'haskell-error-face haskell-check-error-fringe)))
(overlay-put ovl 'before-string fringe)
(overlay-put ovl 'face face))))))
(defun haskell-process-errors-warnings
(module-buffer session process buffer &optional return-only)
"Trigger handling type errors or warnings.
Either prints the messages in the interactive buffer or if CONT
is specified, passes the error onto that.
When MODULE-BUFFER is non-NIL, paint error overlays."
(save-excursion
(cond
((haskell-process-consume
process
"\\(Module imports form a cycle:[ \n]+module [^ ]+ ([^)]+)[[:unibyte:][:nonascii:]]+?\\)\nFailed")
(let ((err (match-string 1 buffer)))
(if (string-match "module [`']\\([^ ]+\\)['`] (\\([^)]+\\))" err)
(let* ((default-directory (haskell-session-current-dir session))
(module (match-string 1 err))
(file (match-string 2 err))
(relative-file-name (file-relative-name file)))
(unless return-only
(haskell-interactive-show-load-message
session
'import-cycle
module
relative-file-name
nil
nil)
(haskell-interactive-mode-compile-error
session
(format "%s:1:0: %s"
relative-file-name
err)))
(list :file file :line 1 :col 0 :msg err :type 'error))
t)))
((haskell-process-consume
process
(concat "[\r\n]\\([A-Z]?:?[^ \r\n:][^:\n\r]+\\):\\([0-9()-:]+\\):"
"[ \n\r]+\\([[:unibyte:][:nonascii:]]+?\\)\n[^ ]"))
(haskell-process-set-response-cursor
process
(- (haskell-process-response-cursor process) 1))
(let* ((buffer (haskell-process-response process))
(file (match-string 1 buffer))
(location-raw (match-string 2 buffer))
(error-msg (match-string 3 buffer))
(type (cond ((string-match "^[Ww]arning:" error-msg) 'warning)
((string-match "^Splicing " error-msg) 'splice)
(t 'error)))
(critical (not (eq type 'warning)))
;; XXX: extract hole information, pass down to
;; `haskell-check-paint-overlay'
(final-msg (format "%s:%s: %s"
(haskell-session-strip-dir session file)
location-raw
error-msg))
(location (haskell-process-parse-error
(concat file ":" location-raw ": x")))
(line (plist-get location :line))
(col1 (plist-get location :col)))
(when (and module-buffer haskell-process-show-overlays)
(haskell-check-paint-overlay
module-buffer
(string= (file-truename (buffer-file-name module-buffer))
(file-truename file))
line error-msg file type nil col1))
(if return-only
(list :file file :line line :col col1 :msg error-msg :type type)
(progn (funcall (cl-case type
(warning 'haskell-interactive-mode-compile-warning)
(splice 'haskell-interactive-mode-compile-splice)
(error 'haskell-interactive-mode-compile-error))
session final-msg)
(when critical
(haskell-mode-message-line final-msg))
(haskell-process-trigger-suggestions
session
error-msg
file
line)
t)))))))
(defun haskell-interactive-show-load-message (session type module-name file-name echo th)
"Show the '(Compiling|Loading) X' message."
(let ((msg (concat
(cl-ecase type
('compiling
(if haskell-interactive-mode-include-file-name
(format "Compiling: %s (%s)" module-name file-name)
(format "Compiling: %s" module-name)))
('loading (format "Loading: %s" module-name))
('import-cycle
(format "Module has an import cycle: %s" module-name)))
(if th " [TH]" ""))))
(haskell-mode-message-line msg)
(when haskell-interactive-mode-delete-superseded-errors
(haskell-interactive-mode-delete-compile-messages session file-name))
(when echo
(haskell-interactive-mode-echo session msg))))
;;;###autoload
(defun haskell-process-reload-devel-main ()
"Reload the module `DevelMain' and then run `DevelMain.update'.
This is for doing live update of the code of servers or GUI
applications. Put your development version of the program in
`DevelMain', and define `update' to auto-start the program on a
new thread, and use the `foreign-store' package to access the
running context across :load/:reloads in GHCi."
(interactive)
(haskell-mode-toggle-interactive-prompt-state)
(unwind-protect
(with-current-buffer
(or (get-buffer "DevelMain.hs")
(if (y-or-n-p
"You need to open a buffer named DevelMain.hs. Find now?")
(ido-find-file)
(error "No DevelMain.hs buffer.")))
(let ((session (haskell-interactive-session)))
(let ((process (haskell-interactive-process)))
(haskell-process-queue-command
process
(make-haskell-command
:state (list :session session
:process process
:buffer (current-buffer))
:go (lambda (state)
(haskell-process-send-string (plist-get state ':process)
":l DevelMain"))
:live (lambda (state buffer)
(haskell-process-live-build (plist-get state ':process)
buffer
nil))
:complete (lambda (state response)
(haskell-process-load-complete
(plist-get state ':session)
(plist-get state ':process)
response
nil
(plist-get state ':buffer)
(lambda (ok)
(when ok
(haskell-process-queue-without-filters
(haskell-interactive-process)
"DevelMain.update")
(message "DevelMain updated."))))))))))
(haskell-mode-toggle-interactive-prompt-state t)))
(provide 'haskell-load)
;;; haskell-load.el ends here

Binary file not shown.

View File

@@ -0,0 +1,162 @@
;;; haskell-menu.el --- A Haskell sessions menu -*- lexical-binding: t -*-
;; Copyright (C) 2013 Chris Done
;; Author: Chris Done <chrisdone@gmail.com>
;; This file is not part of GNU Emacs.
;; 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:
;;; Todo:
;;; Code:
(require 'cl-lib)
(require 'haskell-compat)
(require 'haskell-session)
(require 'haskell-process)
(require 'haskell-interactive-mode)
(defcustom haskell-menu-buffer-name "*haskell-menu*"
"The name of the Haskell session menu buffer"
:group 'haskell-interactive
:type 'string)
;;;###autoload
(defun haskell-menu ()
"Launch the Haskell sessions menu."
(interactive)
(or (get-buffer haskell-menu-buffer-name)
(with-current-buffer (get-buffer-create haskell-menu-buffer-name)
(haskell-menu-mode)))
(switch-to-buffer-other-window (get-buffer haskell-menu-buffer-name))
(haskell-menu-revert-function nil nil))
(defvar haskell-menu-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "n") 'next-line)
(define-key map (kbd "p") 'previous-line)
(define-key map (kbd "RET") 'haskell-menu-mode-ret)
map)
"Keymap for `haskell-menu-mode'.")
(define-derived-mode haskell-menu-mode special-mode "Haskell Session Menu"
"Major mode for managing Haskell sessions.
Each line describes one session.
Letters do not insert themselves; instead, they are commands."
(setq buffer-read-only t)
(setq-local revert-buffer-function 'haskell-menu-revert-function)
(setq truncate-lines t)
(haskell-menu-revert-function nil t))
(suppress-keymap haskell-menu-mode-map t)
(defun haskell-menu-revert-function (_arg1 _arg2)
"Function to refresh the display."
(let ((buffer-read-only nil)
(orig-line (line-number-at-pos))
(orig-col (current-column)))
(or (eq buffer-undo-list t)
(setq buffer-undo-list nil))
(erase-buffer)
(haskell-menu-insert-menu)
(goto-char (point-min))
(forward-line (1- orig-line))
(forward-char orig-col)))
(defun haskell-menu-insert-menu ()
"Insert the Haskell sessions menu to the current buffer."
(if (null haskell-sessions)
(insert "No Haskell sessions.")
(haskell-menu-tabulate
(list "Name" "PID" "Time" "RSS" "Cabal directory" "Working directory" "Command")
(mapcar (lambda (session)
(let ((process (haskell-process-process (haskell-session-process session))))
(cond
(process
(let ((id (process-id process)))
(list (propertize (haskell-session-name session) 'face 'buffer-menu-buffer)
(if (process-live-p process) (number-to-string id) "-")
(if (process-live-p process)
(format-time-string "%H:%M:%S"
(encode-time (cl-caddr (assoc 'etime (process-attributes id)))
0 0 0 0 0))
"-")
(if (process-live-p process)
(concat (number-to-string (/ (cdr (assoc 'rss (process-attributes id)))
1024))
"MB")
"-")
(haskell-session-cabal-dir session)
(haskell-session-current-dir session)
(mapconcat 'identity (process-command process) " "))))
(t (list (propertize (haskell-session-name session) 'face 'buffer-menu-buffer)
""
""
""
(haskell-session-cabal-dir session)
(haskell-session-current-dir session))))))
haskell-sessions))))
(defun haskell-menu-tabulate (headings rows)
"Prints a list of lists as a formatted table to the current buffer."
(let* ((columns (length headings))
(widths (make-list columns 0)))
;; Calculate column widths. This is kind of hideous.
(dolist (row rows)
(setq widths
(let ((list (list)))
(dotimes (i columns)
(setq list (cons (max (nth i widths)
(1+ (length (nth i row)))
(1+ (length (nth i headings))))
list)))
(reverse list))))
;; Print headings.
(let ((heading (propertize " " 'display '(space :align-to 0))))
(dotimes (i columns)
(setq heading (concat heading
(format (concat "%-" (number-to-string (nth i widths)) "s")
(nth i headings)))))
(setq header-line-format heading))
;; Print tabulated rows.
(dolist (row rows)
(dotimes (i columns)
(insert (format (concat "%-" (number-to-string (nth i widths)) "s")
(nth i row))))
(insert "\n"))))
(defun haskell-menu-mode-ret ()
"Handle RET key."
(interactive)
(let* ((name (save-excursion
(goto-char (line-beginning-position))
(buffer-substring-no-properties (point)
(progn (search-forward " ")
(forward-char -1)
(point)))))
(session (car (cl-remove-if-not (lambda (session)
(string= (haskell-session-name session)
name))
haskell-sessions))))
(switch-to-buffer (haskell-session-interactive-buffer session))))
(provide 'haskell-menu)
;;; haskell-menu.el ends here

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,8 @@
(define-package "haskell-mode" "20171022.26" "A Haskell editing mode"
'((emacs "24.3"))
:keywords
'("haskell" "cabal" "ghc" "repl")
:url "https://github.com/haskell/haskell-mode")
;; Local Variables:
;; no-byte-compile: t
;; End:

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,117 @@
;;; haskell-modules.el --- -*- lexical-binding: t -*-
;; Copyright (c) 2014 Chris Done. All rights reserved.
;; 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 this program. If not, see <http://www.gnu.org/licenses/>.
;;; Code:
(require 'haskell-sort-imports)
(require 'haskell-align-imports)
(require 'haskell-session)
(require 'haskell-navigate-imports)
(require 'haskell-complete-module)
(require 'haskell-sandbox)
(require 'haskell-customize)
(defun haskell-add-import (&optional module)
"Add an import to the import list. Sorts and aligns imports,
unless `haskell-stylish-on-save' is set, in which case we defer
to stylish-haskell."
(interactive)
(save-excursion
(goto-char (point-max))
(haskell-navigate-imports)
(insert (haskell-import-for-module
(or module
(haskell-complete-module-read
"Module: "
(haskell-session-all-modules (haskell-modules-session))))))
(unless haskell-stylish-on-save (haskell-sort-imports)
(haskell-align-imports))))
(defun haskell-import-for-module (module)
"Get import statements for the given module."
(let ((mapping (assoc module haskell-import-mapping)))
(if mapping
(cdr mapping)
(concat (read-from-minibuffer "Import line: "
(format "import %s" module))
"\n"))))
;;;###autoload
(defun haskell-session-installed-modules (_session &optional _dontcreate)
"Get the modules installed in the current package set."
;; TODO: Again, this makes HEAVY use of unix utilities. It'll work
;; fine in Linux, probably okay on OS X, and probably not at all on
;; Windows. Again, if someone wants to test on Windows and come up
;; with alternatives that's OK.
;;
;; Ideally all these package queries can be provided by a Haskell
;; program based on the Cabal API. Possibly as a nice service. Such
;; a service could cache and do nice things like that. For now, this
;; simple shell script takes us far.
;;
;; Probably also we can take the code from inferior-haskell-mode.
;;
;; Ugliness aside, if it saves us time to type it's a winner.
;;
;; FIXME/TODO: add support for (eq 'cabal-repl (haskell-process-type))
(let ((session (haskell-session-maybe)))
(when session
(let ((modules (shell-command-to-string
(format "%s 2> /dev/null | %s | %s"
(cond
((haskell-sandbox-exists-p session)
(concat "ghc-pkg dump -f "
(shell-quote-argument (haskell-sandbox-pkgdb session))))
(t "ghc-pkg dump"))
"egrep '^(exposed-modules: | )[A-Z]'"
"cut -c18-"))))
(split-string modules)))))
;;;###autoload
(defun haskell-session-all-modules (session &optional dontcreate)
"Get all modules -- installed or in the current project.
If DONTCREATE is non-nil don't create a new session."
(append (haskell-session-installed-modules session dontcreate)
(haskell-session-project-modules session dontcreate)))
;;;###autoload
(defun haskell-session-project-modules (session &optional dontcreate)
"Get the modules of the current project.
If DONTCREATE is non-nil don't create a new session."
(if (or (not dontcreate) (haskell-session-maybe))
(let* ((modules
(shell-command-to-string
(format "%s && %s"
(format "cd %s" (haskell-session-cabal-dir session))
;; TODO: Use a different, better source. Possibly hasktags or some such.
;; TODO: At least make it cross-platform. Linux
;; (and possibly OS X) have egrep, Windows
;; doesn't -- or does it via Cygwin or MinGW?
;; This also doesn't handle module\nName. But those gits can just cut it out!
"egrep '^module[\t\r ]+[^(\t\r ]+' . -r -I --include='*.*hs' --include='*.hsc' -s -o -h | sed 's/^module[\t\r ]*//' | sort | uniq"))))
(split-string modules))))
(defun haskell-modules-session ()
"Get the `haskell-session', throw an error if it's not
available."
(or (haskell-session-maybe)
(haskell-session-assign
(or (haskell-session-from-buffer)
(haskell-session-choose)
(error "No session associated with this buffer. Try M-x haskell-session-change or report this as a bug.")))))
(provide 'haskell-modules)

Binary file not shown.

View File

@@ -0,0 +1,130 @@
;;; haskell-move-nested.el --- Change the column of text nested below a line -*- lexical-binding: t -*-
;; Copyright (C) 2010 Chris Done
;; Author: Chris Done <chrisdone@gmail.com>
;; This file is not part of GNU Emacs.
;; This program 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 of
;; the License, or (at your option) any later version.
;; This program 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 this program. If not, see
;; <http://www.gnu.org/licenses/>.
;;; Commentary:
;; This module is intended for Haskell mode users, but is
;; independent of Haskell mode.
;; Example usage:
;; (define-key haskell-mode-map (kbd "C-,") 'haskell-move-nested-left)
;; (define-key haskell-mode-map (kbd "C-.") 'haskell-move-nested-right)
;;; Code:
;;;###autoload
(defun haskell-move-nested (cols)
"Shift the nested off-side-rule block adjacent to point by COLS columns to the right.
In Transient Mark mode, if the mark is active, operate on the contents
of the region instead.
"
(save-excursion
(if (and transient-mark-mode mark-active)
(progn
(indent-rigidly (region-beginning) (region-end) cols)
(setq deactivate-mark nil))
(let ((region (haskell-move-nested-region)))
(when region
(indent-rigidly (car region) (cdr region) cols))))))
;;;###autoload
(defun haskell-move-nested-right (cols)
"Increase indentation of the following off-side-rule block adjacent to point.
Use a numeric prefix argument to indicate amount of indentation to apply.
In Transient Mark mode, if the mark is active, operate on the contents
of the region instead."
(interactive "p")
(haskell-move-nested cols)
)
;;;###autoload
(defun haskell-move-nested-left (cols)
"Decrease indentation of the following off-side-rule block adjacent to point.
Use a numeric prefix argument to indicate amount of indentation to apply.
In Transient Mark mode, if the mark is active, operate on the contents
of the region instead."
(interactive "p")
(haskell-move-nested (- cols))
)
(defun haskell-move-nested-region ()
"Infer region off-side-rule block adjacent to point.
Used by `haskell-move-nested'.
"
(save-excursion
(let ((starting-level (current-column)))
(forward-line)
(let ((current-level (haskell-move-nested-indent-level)))
(let ((start-point (line-beginning-position))
(start-end-point (line-end-position))
(end-point nil)
(last-line 0))
(forward-line)
(while (and (not (= (line-beginning-position) last-line))
(or (> (haskell-move-nested-indent-level) starting-level)
(and (> current-level starting-level)
(>= (haskell-move-nested-indent-level) current-level))))
(setq last-line (line-beginning-position))
(setq end-point (line-end-position))
(forward-line))
(cons start-point (or end-point
start-end-point)))))))
(defun haskell-move-nested-indent-level ()
(max
0
(1- (length
(buffer-substring-no-properties
(line-beginning-position)
(or (save-excursion (goto-char (line-beginning-position))
(search-forward-regexp "[^ ]" (line-end-position) t 1))
(line-beginning-position)))))))
(defun haskell-kill-nested ()
"Kill the nested region after point."
(interactive)
(let ((start (point))
(reg (save-excursion
(search-backward-regexp "^[ ]+" (line-beginning-position) t 1)
(search-forward-regexp "[^ ]" (line-end-position) t 1)
(haskell-move-nested-region))))
(kill-region start (cdr reg))))
(defun haskell-delete-nested ()
"Kill the nested region after point."
(interactive)
(let ((start (point))
(reg (save-excursion
(search-backward-regexp "^[ ]+" (line-beginning-position) t 1)
(search-forward-regexp "[^ ]" (line-end-position) t 1)
(haskell-move-nested-region))))
(delete-region start (cdr reg))))
(provide 'haskell-move-nested)
;;; haskell-move-nested.el ends here

Binary file not shown.

View File

@@ -0,0 +1,122 @@
;;; haskell-navigate-imports.el --- A function for cycling through Haskell import lists -*- lexical-binding: t -*-
;; Copyright (C) 2010 Chris Done
;; Author: Chris Done <chrisdone@gmail.com>
;; This file is not part of GNU Emacs.
;; This program 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 of
;; the License, or (at your option) any later version.
;; This program 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 this program. If not, see
;; <http://www.gnu.org/licenses/>.
;;; Commentary:
;; The cycling step will stop once at the last import list so
;; that it is easy to add a new import list.
;; This module works completely independently of any libraries
;; (including haskell-mode).
;; Exports three interactive functions:
;; 1. haskell-navigate-imports
;; 2. haskell-navigate-imports-go
;; 3. haskell-navigate-imports-return
;; Example usage:
;; (require 'haskell-navigate-imports)
;; (define-key haskell-mode-map (kbd "<f8>") 'haskell-navigate-imports)
;;; Code:
(defvar haskell-navigate-imports-start-point nil)
;;;###autoload
(defun haskell-navigate-imports (&optional return)
"Cycle the Haskell import lines or return to point (with prefix arg)."
(interactive "P")
(if return
(haskell-navigate-imports-return)
(haskell-navigate-imports-go)))
;;;###autoload
(defun haskell-navigate-imports-go ()
"Go to the first line of a list of consecutive import lines. Cycles."
(interactive)
(unless (or (haskell-navigate-imports-line)
(equal (line-beginning-position) (point-min))
(save-excursion (forward-line -1)
(haskell-navigate-imports-line)))
(setq haskell-navigate-imports-start-point (point)))
(haskell-navigate-imports-go-internal))
;;;###autoload
(defun haskell-navigate-imports-return ()
"Return to the non-import point we were at before going to the module list.
If we were originally at an import list, we can just cycle through easily."
(interactive)
(when haskell-navigate-imports-start-point
(goto-char haskell-navigate-imports-start-point)))
(defun haskell-navigate-imports-go-internal ()
"Go to the first line of a list of consecutive import lines. Cycle."
(if (haskell-navigate-imports-line)
(progn (haskell-navigate-imports-goto-end)
(when (haskell-navigate-imports-find-forward-line)
(haskell-navigate-imports-go-internal)))
(let ((point (haskell-navigate-imports-find-forward-line)))
(if point
(goto-char point)
(progn (goto-char (point-min))
(if (haskell-navigate-imports-find-forward-line)
(haskell-navigate-imports-go-internal)
(when (search-forward-regexp "^module" nil t 1)
(search-forward "\n\n" nil t 1))))))))
(defun haskell-navigate-imports-goto-end ()
"Skip a bunch of consecutive import lines."
(while (not (or (equal (point)
(point-max))
(not (haskell-navigate-imports-line))))
(forward-line)))
(defun haskell-navigate-imports-find-forward-line ()
"Return a point with at an import line, or nothing."
(save-excursion
(while (not (or (equal (point) (point-max))
(haskell-navigate-imports-after-imports-p) ;; This one just speeds it up.
(haskell-navigate-imports-line)))
(forward-line))
(if (haskell-navigate-imports-line)
(point)
nil)))
(defun haskell-navigate-imports-line ()
"Try to match the current line as a regexp."
(let ((line (buffer-substring-no-properties (line-beginning-position)
(line-end-position))))
(if (string-match "^import " line)
line
nil)))
(defun haskell-navigate-imports-after-imports-p ()
"Are we after the imports list? Just for a speed boost."
(save-excursion
(goto-char (line-beginning-position))
(not (not (search-forward-regexp "\\( = \\|\\<instance\\>\\| :: \\)"
(line-end-position) t 1)))))
(provide 'haskell-navigate-imports)
;;; haskell-navigate-imports.el ends here

View File

@@ -0,0 +1,104 @@
;;; haskell-presentation-mode.el --- Presenting Haskell things -*- lexical-binding: t -*-
;; Copyright (C) 2013 Chris Done
;; Author: Chris Done <chrisdone@gmail.com>
;; This file is not part of GNU Emacs.
;; 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:
;;; Code:
(require 'haskell-mode)
(require 'haskell-session)
(defvar haskell-presentation-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "q") 'quit-window)
(define-key map (kbd "c") 'haskell-presentation-clear)
map)
"Keymap for `haskell-presentation-mode'.")
(define-derived-mode haskell-presentation-mode
haskell-mode "Presentation"
"Major mode for viewing Haskell snippets.
\\{hypertext-mode-map}"
(setq case-fold-search nil))
(defconst haskell-presentation-buffer-name
"*Haskell Presentation*"
"Haskell Presentation buffer name.")
(defconst haskell-presentation-hint-message
"-- Hit `q' to close this window; `c' to clear.\n\n"
"Hint message appered in Haskell Presentation buffer.")
(defun haskell-presentation-buffer ()
"Return Haskell Presentaion buffer.
Return current presenation buffer or create new one if absent.
Never returns nil."
;; TODO Provide interactive calling options: when called interactively make
;; the presentation buffer current.
(let ((may-buffer (get-buffer haskell-presentation-buffer-name)))
(if may-buffer
may-buffer
(let ((buffer (generate-new-buffer haskell-presentation-buffer-name)))
(with-current-buffer buffer
(insert haskell-presentation-hint-message)
(haskell-presentation-mode)
(setq buffer-read-only t))
buffer))))
(defun haskell-presentation-clear ()
"Clear Haskell Presentation buffer."
(interactive)
(let ((hp-buf (get-buffer haskell-presentation-buffer-name)))
(when hp-buf
(with-current-buffer hp-buf
(let ((buffer-read-only nil))
(erase-buffer)
(insert haskell-presentation-hint-message))))))
(defun haskell-presentation-present (session code &optional clear)
"Present given code in a popup buffer.
Creates temporal Haskell Presentation buffer and assigns it to
given haskell SESSION; presented CODE will be fontified as
haskell code. Give an optional non-nil CLEAR arg to clear the
buffer before presenting message."
(let ((buffer (haskell-presentation-buffer)))
(with-current-buffer buffer
(when (boundp 'shm-display-quarantine)
(setq-local shm-display-quarantine nil))
(when clear (haskell-presentation-clear))
(haskell-session-assign session)
(goto-char (point-min))
(forward-line 2)
(save-excursion
(let ((buffer-read-only nil))
(insert code "\n\n"))))
(if (eq major-mode 'haskell-presentation-mode)
(switch-to-buffer buffer)
(pop-to-buffer buffer))))
(provide 'haskell-presentation-mode)
;;; haskell-presentation-mode.el ends here

View File

@@ -0,0 +1,510 @@
;;; haskell-process.el --- Communicating with the inferior Haskell process -*- lexical-binding: t -*-
;; Copyright (C) 2011 Chris Done
;; Author: Chris Done <chrisdone@gmail.com>
;; This file is not part of GNU Emacs.
;; 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.
;;; Code:
(require 'cl-lib)
(require 'json)
(require 'url-util)
(require 'haskell-compat)
(require 'haskell-session)
(require 'haskell-customize)
(require 'haskell-string)
(defconst haskell-process-prompt-regex "\4"
"Used for delimiting command replies. 4 is End of Transmission.")
(defvar haskell-reload-p nil
"Used internally for `haskell-process-loadish'.")
(defconst haskell-process-greetings
(list "Hello, Haskell!"
"The lambdas must flow."
"Hours of hacking await!"
"The next big Haskell project is about to start!"
"Your wish is my IO ().")
"Greetings for when the Haskell process starts up.")
(defconst haskell-process-logo
(expand-file-name "logo.svg" haskell-mode-pkg-base-dir)
"Haskell logo for notifications.")
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Accessing commands -- using cl 'defstruct'
(cl-defstruct haskell-command
"Data structure representing a command to be executed when with
a custom state and three callback."
;; hold the custom command state
;; state :: a
state
;; called when to execute a command
;; go :: a -> ()
go
;; called whenever output was collected from the haskell process
;; live :: a -> Response -> Bool
live
;; called when the output from the haskell process indicates that the command
;; is complete
;; complete :: a -> Response -> ()
complete)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Building the process
(defun haskell-process-compute-process-log-and-command (session hptype)
"Compute the log and process to start command for the SESSION from the HPTYPE.
Do not actually start any process.
HPTYPE is the result of calling `'haskell-process-type`' function."
(let ((session-name (haskell-session-name session)))
(cl-ecase hptype
('ghci
(append (list (format "Starting inferior GHCi process %s ..."
haskell-process-path-ghci)
session-name
nil)
(apply haskell-process-wrapper-function
(list
(append (haskell-process-path-to-list haskell-process-path-ghci)
haskell-process-args-ghci)))))
('cabal-new-repl
(append (list (format "Starting inferior `cabal new-repl' process using %s ..."
haskell-process-path-cabal)
session-name
nil)
(apply haskell-process-wrapper-function
(list
(append
(haskell-process-path-to-list haskell-process-path-cabal)
(list "new-repl")
haskell-process-args-cabal-new-repl
(let ((target (haskell-session-target session)))
(if target (list target) nil)))))))
('cabal-repl
(append (list (format "Starting inferior `cabal repl' process using %s ..."
haskell-process-path-cabal)
session-name
nil)
(apply haskell-process-wrapper-function
(list
(append
(haskell-process-path-to-list haskell-process-path-cabal)
(list "repl")
haskell-process-args-cabal-repl
(let ((target (haskell-session-target session)))
(if target (list target) nil)))))))
('stack-ghci
(append (list (format "Starting inferior stack GHCi process using %s" haskell-process-path-stack)
session-name
nil)
(apply haskell-process-wrapper-function
(list
(append
(haskell-process-path-to-list haskell-process-path-stack)
(list "ghci")
(let ((target (haskell-session-target session)))
(if target (list target) nil))
haskell-process-args-stack-ghci))))))))
(defun haskell-process-path-to-list (path)
"Convert a path (which may be a string or a list) to a list."
(if (stringp path)
(list path)
path))
(defun haskell-process-make (name)
"Make an inferior Haskell process."
(list (cons 'name name)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Process communication
(defun haskell-process-sentinel (proc event)
"The sentinel for the process pipe."
(let ((session (haskell-process-project-by-proc proc)))
(when session
(let* ((process (haskell-session-process session)))
(unless (haskell-process-restarting process)
(haskell-process-log
(propertize (format "Event: %S\n" event)
'face '((:weight bold))))
(haskell-process-log
(propertize "Process reset.\n"
'face 'font-lock-comment-face))
(run-hook-with-args 'haskell-process-ended-functions process))))))
(defun haskell-process-filter (proc response)
"The filter for the process pipe."
(let ((i 0))
(cl-loop for line in (split-string response "\n")
do (haskell-process-log
(concat (if (= i 0)
(propertize "<- " 'face 'font-lock-comment-face)
" ")
(propertize line 'face 'haskell-interactive-face-compile-warning)))
do (setq i (1+ i))))
(let ((session (haskell-process-project-by-proc proc)))
(when session
(if (haskell-process-cmd (haskell-session-process session))
(haskell-process-collect session
response
(haskell-session-process session))))))
(defun haskell-process-log (msg)
"Effective append MSG to the process log (if enabled)."
(when haskell-process-log
(let* ((append-to (get-buffer-create "*haskell-process-log*")))
(with-current-buffer append-to
;; point should follow insertion so that it stays at the end
;; of the buffer
(setq-local window-point-insertion-type t)
(let ((buffer-read-only nil))
(insert msg "\n"))))))
(defun haskell-process-project-by-proc (proc)
"Find project by process."
(cl-find-if (lambda (project)
(string= (haskell-session-name project)
(process-name proc)))
haskell-sessions))
(defun haskell-process-collect (_session response process)
"Collect input for the response until receives a prompt."
(haskell-process-set-response process
(concat (haskell-process-response process) response))
(while (haskell-process-live-updates process))
(when (string-match haskell-process-prompt-regex
(haskell-process-response process))
(haskell-command-exec-complete
(haskell-process-cmd process)
(replace-regexp-in-string
haskell-process-prompt-regex
""
(haskell-process-response process)))
(haskell-process-reset process)
(haskell-process-trigger-queue process)))
(defun haskell-process-reset (process)
"Reset the process's state, ready for the next send/reply."
(progn (haskell-process-set-response-cursor process 0)
(haskell-process-set-response process "")
(haskell-process-set-cmd process nil)))
(defun haskell-process-consume (process regex)
"Consume a regex from the response and move the cursor along if succeed."
(when (string-match regex
(haskell-process-response process)
(haskell-process-response-cursor process))
(haskell-process-set-response-cursor process (match-end 0))
t))
(defun haskell-process-send-string (process string)
"Try to send a string to the process's process. Ask to restart if it's not running."
(let ((child (haskell-process-process process)))
(if (equal 'run (process-status child))
(let ((out (concat string "\n")))
(let ((i 0))
(cl-loop for line in (split-string out "\n")
do (unless (string-equal "" line)
(haskell-process-log
(concat (if (= i 0)
(propertize "-> " 'face 'font-lock-comment-face)
" ")
(propertize line 'face 'font-lock-string-face))))
do (setq i (1+ i))))
(process-send-string child out))
(unless (haskell-process-restarting process)
(run-hook-with-args 'haskell-process-ended-functions process)))))
(defun haskell-process-live-updates (process)
"Process live updates."
(haskell-command-exec-live (haskell-process-cmd process)
(haskell-process-response process)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Making commands
(defun haskell-process-queue-without-filters (process line)
"Queue LINE to be sent to PROCESS without bothering to look at
the response."
(haskell-process-queue-command
process
(make-haskell-command
:state (cons process line)
:go (lambda (state)
(haskell-process-send-string (car state)
(cdr state))))))
(defun haskell-process-queue-command (process command)
"Add a command to the process command queue."
(haskell-process-cmd-queue-add process command)
(haskell-process-trigger-queue process))
(defun haskell-process-trigger-queue (process)
"Trigger the next command in the queue to be ran if there is no current command."
(if (and (haskell-process-process process)
(process-live-p (haskell-process-process process)))
(unless (haskell-process-cmd process)
(let ((cmd (haskell-process-cmd-queue-pop process)))
(when cmd
(haskell-process-set-cmd process cmd)
(haskell-command-exec-go cmd))))
(progn (haskell-process-reset process)
(haskell-process-set process 'command-queue nil)
(run-hook-with-args 'haskell-process-ended-functions process))))
(defun haskell-process-queue-flushed-p (process)
"Return t if command queue has been completely processed."
(not (or (haskell-process-cmd-queue process)
(haskell-process-cmd process))))
(defun haskell-process-queue-flush (process)
"Block till PROCESS' command queue has been completely processed.
This uses `accept-process-output' internally."
(while (not (haskell-process-queue-flushed-p process))
(haskell-process-trigger-queue process)
(accept-process-output (haskell-process-process process) 1)))
(defun haskell-process-queue-sync-request (process reqstr)
"Queue submitting REQSTR to PROCESS and return response blockingly."
(let ((cmd (make-haskell-command
:state (cons nil process)
:go `(lambda (s) (haskell-process-send-string (cdr s) ,reqstr))
:complete 'setcar)))
(haskell-process-queue-command process cmd)
(haskell-process-queue-flush process)
(car-safe (haskell-command-state cmd))))
(defun haskell-process-get-repl-completions (process inputstr &optional limit)
"Query PROCESS with `:complete repl ...' for INPUTSTR.
Give optional LIMIT arg to limit completion candidates count,
zero, negative values, and nil means all possible completions.
Returns NIL when no completions found."
(let* ((mlimit (if (and limit (> limit 0))
(concat " " (number-to-string limit) " ")
" "))
(reqstr (concat ":complete repl"
mlimit
(haskell-string-literal-encode inputstr)))
(rawstr (haskell-process-queue-sync-request process reqstr))
(response-status (haskell-utils-repl-response-error-status rawstr)))
(if (eq 'unknown-command response-status)
(error
"GHCi lacks `:complete' support (try installing GHC 7.8+ or ghci-ng)")
(when rawstr
;; parse REPL response if any
(let* ((s1 (split-string rawstr "\r?\n" t))
(cs (mapcar #'haskell-string-literal-decode (cdr s1)))
(h0 (car s1))) ;; "<limit count> <all count> <unused string>"
(unless (string-match
"\\`\\([0-9]+\\) \\([0-9]+\\) \\(\".*\"\\)\\'"
h0)
(error "Invalid `:complete' response"))
(let ((cnt1 (match-string 1 h0))
(h1 (haskell-string-literal-decode (match-string 3 h0))))
(unless (= (string-to-number cnt1) (length cs))
(error "Lengths inconsistent in `:complete' reponse"))
(cons h1 cs)))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Accessing the process
(defun haskell-process-get (process key)
"Get the PROCESS's KEY value.
Returns nil if KEY not set."
(cdr (assq key process)))
(defun haskell-process-set (process key value)
"Set the PROCESS's KEY to VALUE.
Returns newly set VALUE."
(if process
(let ((cell (assq key process)))
(if cell
(setcdr cell value) ; modify cell in-place
(setcdr process (cons (cons key value) (cdr process))) ; new cell
value))
(display-warning 'haskell-interactive
"`haskell-process-set' called with nil process")))
;; Wrappers using haskell-process-{get,set}
(defun haskell-process-set-sent-stdin (p v)
"We've sent stdin, so let's not clear the output at the end."
(haskell-process-set p 'sent-stdin v))
(defun haskell-process-sent-stdin-p (p)
"Did we send any stdin to the process during evaluation?"
(haskell-process-get p 'sent-stdin))
(defun haskell-process-set-suggested-imports (p v)
"Remember what imports have been suggested, to avoid
re-asking about the same imports."
(haskell-process-set p 'suggested-imported v))
(defun haskell-process-suggested-imports (p)
"Get what modules have already been suggested and accepted."
(haskell-process-get p 'suggested-imported))
(defun haskell-process-set-evaluating (p v)
"Set status of evaluating to be on/off."
(haskell-process-set p 'evaluating v))
(defun haskell-process-evaluating-p (p)
"Get status of evaluating (on/off)."
(haskell-process-get p 'evaluating))
(defun haskell-process-set-process (p v)
"Set the process's inferior process."
(haskell-process-set p 'inferior-process v))
(defun haskell-process-process (p)
"Get the process child."
(haskell-process-get p 'inferior-process))
(defun haskell-process-name (p)
"Get the process name."
(haskell-process-get p 'name))
(defun haskell-process-cmd (p)
"Get the process's current command.
Return nil if no current command."
(haskell-process-get p 'current-command))
(defun haskell-process-set-cmd (p v)
"Set the process's current command."
(haskell-process-set-evaluating p nil)
(haskell-process-set-sent-stdin p nil)
(haskell-process-set-suggested-imports p nil)
(haskell-process-set p 'current-command v))
(defun haskell-process-response (p)
"Get the process's current response."
(haskell-process-get p 'current-response))
(defun haskell-process-session (p)
"Get the process's current session."
(haskell-process-get p 'session))
(defun haskell-process-set-response (p v)
"Set the process's current response."
(haskell-process-set p 'current-response v))
(defun haskell-process-set-session (p v)
"Set the process's current session."
(haskell-process-set p 'session v))
(defun haskell-process-response-cursor (p)
"Get the process's current response cursor."
(haskell-process-get p 'current-response-cursor))
(defun haskell-process-set-response-cursor (p v)
"Set the process's response cursor."
(haskell-process-set p 'current-response-cursor v))
;; low-level command queue operations
(defun haskell-process-restarting (process)
"Is the PROCESS restarting?"
(haskell-process-get process 'is-restarting))
(defun haskell-process-cmd-queue (process)
"Get the PROCESS' command queue.
New entries get added to the end of the list. Use
`haskell-process-cmd-queue-add' and
`haskell-process-cmd-queue-pop' to modify the command queue."
(haskell-process-get process 'command-queue))
(defun haskell-process-cmd-queue-add (process cmd)
"Add CMD to end of PROCESS's command queue."
(cl-check-type cmd haskell-command)
(haskell-process-set process
'command-queue
(append (haskell-process-cmd-queue process)
(list cmd))))
(defun haskell-process-cmd-queue-pop (process)
"Pop the PROCESS' next entry from command queue.
Returns nil if queue is empty."
(let ((queue (haskell-process-cmd-queue process)))
(when queue
(haskell-process-set process 'command-queue (cdr queue))
(car queue))))
(defun haskell-process-unignore-file (session file)
"
Note to Windows Emacs hackers:
chmod is how to change the mode of files in POSIX
systems. This will not work on your operating
system.
There is a command a bit like chmod called \"Calcs\"
that you can try using here:
http://technet.microsoft.com/en-us/library/bb490872.aspx
If it works, you can submit a patch to this
function and remove this comment.
"
(shell-command (read-from-minibuffer "Permissions command: "
(concat "chmod 700 "
file)))
(haskell-session-modify
session
'ignored-files
(lambda (files)
(cl-remove-if (lambda (path)
(string= path file))
files))))
(defun haskell-command-exec-go (command)
"Call the command's go function."
(let ((go-func (haskell-command-go command)))
(when go-func
(funcall go-func (haskell-command-state command)))))
(defun haskell-command-exec-complete (command response)
"Call the command's complete function."
(let ((comp-func (haskell-command-complete command)))
(when comp-func
(condition-case-unless-debug e
(funcall comp-func
(haskell-command-state command)
response)
(quit (message "Quit"))
(error (message "Haskell process command errored with: %S" e))))))
(defun haskell-command-exec-live (command response)
"Trigger the command's live updates callback."
(let ((live-func (haskell-command-live command)))
(when live-func
(funcall live-func
(haskell-command-state command)
response))))
(provide 'haskell-process)
;;; haskell-process.el ends here

Binary file not shown.

View File

@@ -0,0 +1,124 @@
;;; haskell-repl.el --- REPL evaluation -*- lexical-binding: t -*-
;; Copyright (c) 2014 Chris Done. All rights reserved.
;; 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 this program. If not, see <http://www.gnu.org/licenses/>.
;;; Code:
(require 'cl-lib)
(require 'haskell-interactive-mode)
(require 'haskell-collapse)
(defun haskell-interactive-handle-expr ()
"Handle an inputted expression at the REPL."
(let ((expr (haskell-interactive-mode-input)))
(if (string= "" (replace-regexp-in-string " " "" expr))
;; Just make a new prompt on space-only input
(progn
(goto-char (point-max))
(insert "\n")
(haskell-interactive-mode-prompt))
(when (haskell-interactive-at-prompt)
(cond
;; If already evaluating, then the user is trying to send
;; input to the REPL during evaluation. Most likely in
;; response to a getLine-like function.
((and (haskell-process-evaluating-p (haskell-interactive-process))
(= (line-end-position) (point-max)))
(goto-char (point-max))
(let ((process (haskell-interactive-process))
(string (buffer-substring-no-properties
haskell-interactive-mode-result-end
(point))))
;; here we need to go to end of line again as evil-mode
;; might have managed to put us one char back
(goto-char (point-max))
(insert "\n")
;; Bring the marker forward
(setq haskell-interactive-mode-result-end
(point-max))
(haskell-process-set-sent-stdin process t)
(haskell-process-send-string process string)))
;; Otherwise we start a normal evaluation call.
(t (setq haskell-interactive-mode-old-prompt-start
(copy-marker haskell-interactive-mode-prompt-start))
(set-marker haskell-interactive-mode-prompt-start (point-max))
(haskell-interactive-mode-history-add expr)
(haskell-interactive-mode-do-expr expr)))))))
(defun haskell-interactive-mode-do-expr (expr)
(cond
((string-match "^:present " expr)
(haskell-interactive-mode-do-presentation (replace-regexp-in-string "^:present " "" expr)))
(t
(haskell-interactive-mode-run-expr expr))))
(defun haskell-interactive-mode-run-expr (expr)
"Run the given expression."
(let ((session (haskell-interactive-session))
(process (haskell-interactive-process)))
(haskell-process-queue-command
process
(make-haskell-command
:state (list session process expr 0)
:go (lambda (state)
(goto-char (point-max))
(insert "\n")
(setq haskell-interactive-mode-result-end
(point-max))
(haskell-process-send-string (cadr state)
(haskell-interactive-mode-multi-line (cl-caddr state)))
(haskell-process-set-evaluating (cadr state) t))
:live (lambda (state buffer)
(unless (and (string-prefix-p ":q" (cl-caddr state))
(string-prefix-p (cl-caddr state) ":quit"))
(let* ((cursor (cl-cadddr state))
(next (replace-regexp-in-string
haskell-process-prompt-regex
""
(substring buffer cursor))))
(haskell-interactive-mode-eval-result (car state) next)
(setf (cl-cdddr state) (list (length buffer)))
nil)))
:complete
(lambda (state response)
(haskell-process-set-evaluating (cadr state) nil)
(unless (haskell-interactive-mode-trigger-compile-error state response)
(haskell-interactive-mode-expr-result state response)))))))
(defun haskell-interactive-mode-expr-result (state response)
"Print the result of evaluating the expression."
(let ((response
(with-temp-buffer
(insert response)
(haskell-interactive-mode-handle-h)
(buffer-string))))
(when haskell-interactive-mode-eval-mode
(unless (haskell-process-sent-stdin-p (cadr state))
(haskell-interactive-mode-eval-as-mode (car state) response))))
(haskell-interactive-mode-prompt (car state)))
(defun haskell-interactive-mode-eval-as-mode (session text)
"Insert TEXT font-locked according to `haskell-interactive-mode-eval-mode'."
(with-current-buffer (haskell-session-interactive-buffer session)
(let ((inhibit-read-only t))
(delete-region (1+ haskell-interactive-mode-prompt-start) (point))
(goto-char (point-max))
(insert (haskell-fontify-as-mode text
haskell-interactive-mode-eval-mode))
(when haskell-interactive-mode-collapse
(haskell-hide-toggle)))))
(provide 'haskell-repl)

Binary file not shown.

View File

@@ -0,0 +1,41 @@
;;; haskell-sandbox.el --- Support for sandboxes -*- lexical-binding: t -*-
;; Copyright (c) 2014 Chris Done. All rights reserved.
;; 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 this program. If not, see <http://www.gnu.org/licenses/>.
;;; Code:
(require 'cl-lib)
(require 'haskell-session)
(defun haskell-sandbox-path (session)
"If there is a haskell-session, return the path to the usual sandbox location."
(concat (haskell-session-cabal-dir session)
"/.cabal-sandbox"))
(defun haskell-sandbox-exists-p (session)
"Is there a cabal sandbox?"
(file-exists-p (haskell-sandbox-path session)))
(defun haskell-sandbox-pkgdb (session)
"Get the package database of the sandbox."
(let* ((files (directory-files (haskell-sandbox-path session)))
(dir (car (cl-remove-if-not (lambda (file)
(string-match ".conf.d$" file))
files))))
(when dir
(concat (haskell-sandbox-path session) "/" dir))))
(provide 'haskell-sandbox)

Binary file not shown.

View File

@@ -0,0 +1,227 @@
;;; haskell-session.el --- Haskell sessions -*- lexical-binding: t -*-
;; Copyright (C) 2011-2012 Chris Done
;; Author: Chris Done <chrisdone@gmail.com>
;; This file is not part of GNU Emacs.
;; 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:
;;; Todo:
;;; Code:
(require 'cl-lib)
(require 'haskell-cabal)
(require 'haskell-customize)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Globals
;; Used internally
(defvar-local haskell-session nil)
(defvar haskell-sessions (list)
"All Haskell sessions in the Emacs session.")
(defun haskell-session-tags-filename (session)
"Get the filename for the TAGS file."
(concat (haskell-session-cabal-dir session) "/TAGS"))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Finding/clearing the session
;;;###autoload
(defun haskell-session-maybe ()
"Maybe get the Haskell session, return nil if there isn't one."
(if (default-boundp 'haskell-session)
haskell-session
(setq haskell-session nil)))
(defun haskell-session-from-buffer ()
"Get the session based on the buffer."
(when (and (buffer-file-name)
(consp haskell-sessions))
(cl-reduce (lambda (acc a)
(let ((dir (haskell-session-get a 'cabal-dir)))
(if dir
(if (string-prefix-p dir
(file-name-directory (buffer-file-name)))
(if acc
(if (and
(> (length (haskell-session-get a 'cabal-dir))
(length (haskell-session-get acc 'cabal-dir))))
a
acc)
a)
acc)
acc)))
haskell-sessions
:initial-value nil)))
(defun haskell-session-default-name ()
"Generate a default project name for the new project prompt."
(let ((file (haskell-cabal-find-file)))
(or (when file
(downcase (file-name-sans-extension
(file-name-nondirectory file))))
"haskell")))
(defun haskell-session-assign (session)
"Assing current buffer to SESSION.
This could be helpful for temporary or auxiliary buffers such as
presentation mode buffers (e.g. in case when session is killed
with all relevant buffers)."
(setq-local haskell-session session))
(defun haskell-session-choose ()
"Find a session by choosing from a list of the current sessions."
(when haskell-sessions
(let* ((session-name (funcall haskell-completing-read-function
"Choose Haskell session: "
(cl-remove-if (lambda (name)
(and haskell-session
(string= (haskell-session-name haskell-session)
name)))
(mapcar 'haskell-session-name haskell-sessions))))
(session (cl-find-if (lambda (session)
(string= (haskell-session-name session)
session-name))
haskell-sessions)))
session)))
(defun haskell-session-clear ()
"Clear the buffer of any Haskell session choice."
(setq-local haskell-session nil))
(defun haskell-session-lookup (name)
"Get the session by name."
(cl-remove-if-not (lambda (s)
(string= name (haskell-session-name s)))
haskell-sessions))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Session modules
(defun haskell-session-strip-dir (session file)
"Strip the load dir from the file path."
(let ((cur-dir (haskell-session-current-dir session)))
(if (> (length file) (length cur-dir))
(if (string= (substring file 0 (length cur-dir))
cur-dir)
(replace-regexp-in-string
"^[/\\]" ""
(substring file
(length cur-dir)))
file)
file)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Accessing the session
(defun haskell-session-current-dir (s)
"Get the session current directory."
(let ((dir (haskell-session-get s 'current-dir)))
(or dir
(error "No current directory."))))
(defun haskell-session-name (s)
"Get the session name."
(haskell-session-get s 'name))
(defun haskell-session-target (s)
"Get the session build target.
If `haskell-process-load-or-reload-prompt' is nil, accept `default'."
(let* ((maybe-target (haskell-session-get s 'target))
(target (if maybe-target maybe-target
(let ((new-target
(if haskell-process-load-or-reload-prompt
(read-string "build target (empty for default):")
"")))
(haskell-session-set-target s new-target)))))
(if (not (string= target "")) target nil)))
(defun haskell-session-set-target (s target)
"Set the session build target."
(haskell-session-set s 'target target))
(defun haskell-session-set-interactive-buffer (s v)
"Set the session interactive buffer."
(haskell-session-set s 'interactive-buffer v))
(defun haskell-session-set-process (s v)
"Set the session process."
(haskell-session-set s 'process v))
;;;###autoload
(defun haskell-session-process (s)
"Get the session process."
(haskell-session-get s 'process))
(defun haskell-session-set-cabal-dir (s v)
"Set the session cabal-dir."
(let ((true-path (file-truename v)))
(haskell-session-set s 'cabal-dir true-path)
(haskell-session-set-cabal-checksum s true-path)))
(defun haskell-session-set-current-dir (s v)
"Set the session current directory."
(let ((true-path (file-truename v)))
(haskell-session-set s 'current-dir true-path)))
(defun haskell-session-set-cabal-checksum (s cabal-dir)
"Set the session checksum of .cabal files"
(haskell-session-set s 'cabal-checksum
(haskell-cabal-compute-checksum cabal-dir)))
(defun haskell-session-cabal-dir (s)
"Get the session cabal-dir."
(or (haskell-session-get s 'cabal-dir)
(let ((set-dir (haskell-cabal-get-dir (not haskell-process-load-or-reload-prompt))))
(if set-dir
(progn (haskell-session-set-cabal-dir s set-dir)
set-dir)
(haskell-session-cabal-dir s)))))
(defun haskell-session-modify (session key update)
"Update the value at KEY in SESSION with UPDATE."
(haskell-session-set
session
key
(funcall update
(haskell-session-get session key))))
(defun haskell-session-get (session key)
"Get the SESSION's KEY value.
Returns nil if KEY not set."
(cdr (assq key session)))
(defun haskell-session-set (session key value)
"Set the SESSION's KEY to VALUE.
Returns newly set VALUE."
(let ((cell (assq key session)))
(if cell
(setcdr cell value) ; modify cell in-place
(setcdr session (cons (cons key value) (cdr session))) ; new cell
value)))
(provide 'haskell-session)
;;; haskell-session.el ends here

Binary file not shown.

View File

@@ -0,0 +1,129 @@
;;; haskell-sort-imports.el --- Sort the list of Haskell imports at the point alphabetically -*- lexical-binding: t -*-
;; Copyright (C) 2010 Chris Done
;; Author: Chris Done <chrisdone@gmail.com>
;; This file is not part of GNU Emacs.
;; This program 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 of
;; the License, or (at your option) any later version.
;; This program 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 this program. If not, see
;; <http://www.gnu.org/licenses/>.
;;; Commentary:
;; If the region is active it sorts the imports within the
;; region.
;; This will align and sort the columns of the current import
;; list. It's more or less the coolest thing on the planet.
;;; Code:
(require 'cl-lib)
(defvar haskell-sort-imports-regexp
(concat "^import[ ]+"
"\\(qualified \\)?"
"[ ]*\\(\"[^\"]*\" \\)?"
"[ ]*\\([A-Za-z0-9_.']*.*\\)"))
;;;###autoload
(defun haskell-sort-imports ()
"Sort the import list at point. It sorts the current group
i.e. an import list separated by blank lines on either side.
If the region is active, it will restrict the imports to sort
within that region."
(interactive)
(when (haskell-sort-imports-at-import)
(let* ((points (haskell-sort-imports-decl-points))
(current-string (buffer-substring-no-properties (car points)
(cdr points)))
(current-offset (- (point) (car points))))
(if (region-active-p)
(progn (goto-char (region-beginning))
(haskell-sort-imports-goto-import-start))
(haskell-sort-imports-goto-group-start))
(let* ((start (point))
(imports (haskell-sort-imports-collect-imports))
(sorted (sort (cl-copy-list imports)
(lambda (a b)
(string< (haskell-sort-imports-normalize a)
(haskell-sort-imports-normalize b))))))
(when (not (equal imports sorted))
(delete-region start (point))
(mapc (lambda (import) (insert import "\n")) sorted))
(goto-char start)
(when (search-forward current-string nil t 1)
(forward-char (- (length current-string)))
(forward-char current-offset))))))
(defun haskell-sort-imports-normalize (i)
"Normalize an import, if possible, so that it can be sorted."
(if (string-match haskell-sort-imports-regexp
i)
(match-string 3 i)
i))
(defun haskell-sort-imports-collect-imports ()
(let ((imports (list)))
(while (looking-at "import")
(let* ((points (haskell-sort-imports-decl-points))
(string (buffer-substring-no-properties (car points)
(cdr points))))
(goto-char (min (1+ (cdr points))
(point-max)))
(setq imports (cons string imports))))
(reverse (delq nil (delete-dups imports)))))
(defun haskell-sort-imports-goto-group-start ()
"Go to the start of the import group."
(or (and (search-backward "\n\n" nil t 1)
(goto-char (+ 2 (line-end-position))))
(when (search-backward-regexp "^module " nil t 1)
(goto-char (1+ (line-end-position))))
(goto-char (point-min))))
(defun haskell-sort-imports-at-import ()
"Are we at an import?"
(save-excursion
(haskell-sort-imports-goto-import-start)
(looking-at "import")))
(defun haskell-sort-imports-goto-import-start ()
"Go to the start of the import."
(goto-char (car (haskell-sort-imports-decl-points))))
(defun haskell-sort-imports-decl-points ()
"Get the points of the declaration."
(save-excursion
(let ((start (or (progn (goto-char (line-end-position))
(search-backward-regexp "^[^ \n]" nil t 1)
(unless (or (looking-at "^-}$")
(looking-at "^{-$"))
(point)))
0))
(end (progn (goto-char (1+ (point)))
(or (when (search-forward-regexp "[\n]+[^ \n]" nil t 1)
(forward-char -1)
(search-backward-regexp "[^\n ]" nil t)
(line-end-position))
(when (search-forward-regexp "\n" nil t 1)
(1- (point)))
(point-max)))))
(cons start end))))
(provide 'haskell-sort-imports)
;;; haskell-sort-imports.el ends here

Binary file not shown.

View File

@@ -0,0 +1,219 @@
;;; haskell-string.el --- Haskell related string utilities -*- lexical-binding: t -*-
;; Copyright (C) 2013 Herbert Valerio Riedel
;; Author: Herbert Valerio Riedel <hvr@gnu.org>
;; This file is not part of GNU Emacs.
;; 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 of the License, 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 this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;;; Todo:
;; - write ERT tests
;;; Code:
(require 'cl-lib)
(defun haskell-string-trim (string)
"Remove whitespace around STRING.
A Whitespace character is defined in the Haskell Report as follows
whitechar -> newline | vertab | space | tab | uniWhite
newline -> return linefeed | return | linefeed | formfeed
uniWhite -> any Unicode character defined as whitespace
Note: The implementation currently only supports ASCII
white-space characters, i.e. the implemention doesn't
consider uniWhite."
(let ((s1 (if (string-match "[\t\n\v\f\r ]+\\'" string) (replace-match "" t t string) string)))
(if (string-match "\\`[\t\n\v\f\r ]+" s1) (replace-match "" t t s1) s1)))
(defun haskell-string-only-spaces-p (string)
"Return t if STRING contains only whitespace (or is empty)."
(string= "" (haskell-string-trim string)))
(defun haskell-string-take (string n)
"Return (up to) N character length prefix of STRING."
(substring string 0 (min (length string) n)))
(defconst haskell-string-literal-encode-ascii-array
[ "\\NUL" "\\SOH" "\\STX" "\\ETX" "\\EOT" "\\ENQ" "\\ACK" "\\a" "\\b" "\\t" "\\n" "\\v" "\\f" "\\r" "\\SO" "\\SI" "\\DLE" "\\DC1" "\\DC2" "\\DC3" "\\DC4" "\\NAK" "\\SYN" "\\ETB" "\\CAN" "\\EM" "\\SUB" "\\ESC" "\\FS" "\\GS" "\\RS" "\\US" " " "!" "\\\"" "#" "$" "%" "&" "'" "(" ")" "*" "+" "," "-" "." "/" "0" "1" "2" "3" "4" "5" "6" "7" "8" "9" ":" ";" "<" "=" ">" "?" "@" "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O" "P" "Q" "R" "S" "T" "U" "V" "W" "X" "Y" "Z" "[" "\\\\" "]" "^" "_" "`" "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z" "{" "|" "}" "~" "\\DEL" ]
"Array of encodings for 7-bit ASCII character points indexed by ASCII value.")
(defun haskell-string-literal-encode (str &optional no-quotes)
"Encode STR according Haskell escape rules using 7-bit ASCII representation.
The serialization has been implemented to closely match the
behaviour of GHC's Show instance for Strings.
If NO-QUOTES is non-nil, omit wrapping result in quotes.
This is the dual operation to `haskell-string-literal-decode'."
(let ((lastc -1))
(let ((encode (lambda (c)
(let ((lc lastc))
(setq lastc c)
(if (>= c 128) ;; if non-ASCII code point
(format "\\%d" c)
;; else, for ASCII code points
(if (or (and (= lc 14) (= c ?H)) ;; "\SO\&H"
(and (>= lc 128) (>= c ?0) (<= c ?9))) ;; "\123\&4"
(concat "\\&" (aref haskell-string-literal-encode-ascii-array c))
(aref haskell-string-literal-encode-ascii-array c)
))))))
(if no-quotes
(mapconcat encode str "")
(concat "\"" (mapconcat encode str "") "\"")))))
(defconst haskell-string-literal-escapes-regexp
(concat "[\\]\\(?:"
(regexp-opt (append
(mapcar (lambda (c) (format "%c" c))
"abfnrtv\\\"'&") ;; "charesc" escape sequences
(mapcar (lambda (c) (format "^%c" c))
"ABCDEFGHIJKLMNOPQRSTUVWXYZ@[\\]^_") ;; "cntrl" escape sequences
(mapcar (lambda (s) (format "%s" s))
(split-string "NUL SOH STX ETX EOT ENQ ACK BEL BS HT LF VT FF CR
SO SI DLE DC1 DC2 DC3 DC4 NAK SYN ETB CAN EM SUB ESC
FS GS RS US SP DEL")))) ;; "ascii" (w\o "cntrl") escape sequences
"\\|" "[\t\n\v\f\r ]+[\\]" ;; whitespace gaps
"\\|" "[0-9]+" ;; decimal escape sequence
"\\|" "o[0-7]+" ;; octal escape sequence
"\\|" "x[0-9a-f]+" ;; hex escape sequence
"\\)?") ;; everything else is an invalid escape sequence
"Regexp for matching escape codes in string literals.
See Haskell Report Sect 2.6,
URL `http://www.haskell.org/onlinereport/haskell2010/haskellch2.html#x7-200002.6',
for more details.")
(defconst haskell-string-literal-decode1-table
(let ((h (make-hash-table :test 'equal)))
(mapc (lambda (c) (puthash (concat "\\" (car c)) (cdr c) h))
'(;; ascii-escapes
("NUL" . "\x00") ("SOH" . "\x01") ("STX" . "\x02") ("ETX" . "\x03") ("EOT" . "\x04") ("ENQ" . "\x05")
("ACK" . "\x06") ("BEL" . "\x07") ("BS" . "\x08") ("HT" . "\x09") ("LF" . "\x0a") ("VT" . "\x0b")
("FF" . "\x0c") ("CR" . "\x0d") ("SO" . "\x0e") ("SI" . "\x0f") ("DLE" . "\x10") ("DC1" . "\x11")
("DC2" . "\x12") ("DC3" . "\x13") ("DC4" . "\x14") ("NAK" . "\x15") ("SYN" . "\x16") ("ETB" . "\x17")
("CAN" . "\x18") ("EM" . "\x19") ("SUB" . "\x1a") ("ESC" . "\x1b") ("FS" . "\x1c") ("GS" . "\x1d")
("RS" . "\x1e") ("US" . "\x1f") ("SP" . "\x20") ("DEL" . "\x7f" )
;; C-compatible single-char escape sequences
("a" . "\x07") ("b" . "\x08") ("f" . "\x0c") ("n" . "\x0a") ("r" . "\x0d") ("t" . "\x09") ("v" . "\x0b")
;; trivial escapes
("\\" . "\\") ("\"" . "\"") ("'" . "'")
;; "empty" escape
("&" . "")))
h)
"Hash table containing irregular escape sequences and their decoded strings.
Used by `haskell-string-literal-decode1'.")
(defun haskell-string-literal-decode1 (l)
"Decode a single string literal escape sequence.
L must contain exactly one escape sequence.
This is an internal function used by `haskell-string-literal-decode'."
(let ((case-fold-search nil))
(cond
((gethash l haskell-string-literal-decode1-table))
((string-match "\\`[\\][0-9]+\\'" l) (char-to-string (string-to-number (substring l 1) 10)))
((string-match "\\`[\\]x[[:xdigit:]]+\\'" l) (char-to-string (string-to-number (substring l 2) 16)))
((string-match "\\`[\\]o[0-7]+\\'" l) (char-to-string (string-to-number (substring l 2) 8)))
((string-match "\\`[\\]\\^[@-_]\\'" l) (char-to-string (- (aref l 2) ?@))) ;; "cntrl" escapes
((string-match "\\`[\\][\t\n\v\f\r ]+[\\]\\'" l) "") ;; whitespace gap
(t (error "Invalid escape sequence")))))
(defun haskell-string-literal-decode (estr &optional no-quotes)
"Decode a Haskell string-literal.
If NO-QUOTES is nil, ESTR must be surrounded by quotes.
This is the dual operation to `haskell-string-literal-encode'."
(if (and (not no-quotes)
(string-match-p "\\`\"[^\\\"[:cntrl:]]*\"\\'" estr))
(substring estr 1 -1) ;; optimized fast-path for trivial strings
(let ((s (if no-quotes ;; else: do general decoding
estr
(if (string-match-p "\\`\".*\"\\'" estr)
(substring estr 1 -1)
(error "String literal must be delimited by quotes"))))
(case-fold-search nil))
(replace-regexp-in-string haskell-string-literal-escapes-regexp #'haskell-string-literal-decode1 s t t))))
(defun haskell-string-ellipsize (string n)
"Return STRING truncated to (at most) N characters.
If truncation occured, last character in string is replaced by `…'.
See also `haskell-string-take'."
(cond
((<= (length string) n) string) ;; no truncation needed
((< n 1) "")
(t (concat (substring string 0 (1- n)) ""))))
(defun haskell-string-chomp (str)
"Chomp leading and tailing whitespace from STR."
(while (string-match "\\`\n+\\|^\\s-+\\|\\s-+$\\|\n+\\'"
str)
(setq str (replace-match "" t t str)))
str)
(defun haskell-string-split-to-lines (str)
"Split STR to lines and return a list of strings with preceeding and
succeding space removed."
(when (stringp str)
(cl-mapcar #'haskell-string-chomp (split-string str "\n"))))
(defun haskell-string-trim-prefix (prefix str)
"If PREFIX is prefix of STR, the string is trimmed."
(when (and (stringp prefix)
(stringp str))
(if (string-prefix-p prefix str)
(substring str (length prefix)))))
(defun haskell-string-trim-suffix (suffix str)
"If SUFFIX is suffix of STR, the string is trimmed."
(when (and (stringp suffix)
(stringp str))
(if (string-suffix-p suffix str)
(substring str 0 (* -1 (length suffix))))))
(defun haskell-string-drop-qualifier (ident)
"Drop qualifier from given identifier IDENT.
If the identifier is not qualified return it unchanged."
(or (and (string-match "^\\([^.]*\\.\\)*\\(?1:[^.]+\\)$" ident)
(match-string 1 ident))
ident))
(defun haskell-mode-message-line (str)
"Echo STR in mini-buffer.
Given string is shrinken to single line, multiple lines just
disturbs the programmer."
(message "%s" (haskell-mode-one-line str (frame-width))))
(defun haskell-mode-one-line (str &optional width)
"Try to fit STR as much as possible on one line according to given WIDTH."
(unless width
(setq width (length str)))
(let* ((long-line (replace-regexp-in-string "\n" " " str))
(condensed (replace-regexp-in-string
" +" " " (haskell-string-trim long-line))))
(truncate-string-to-width condensed width nil nil "")))
(provide 'haskell-string)
;;; haskell-string.el ends here

Binary file not shown.

View File

@@ -0,0 +1,300 @@
;;; haskell-unicode-input-method.el --- Haskell Unicode helper functions -*- coding: utf-8; lexical-binding: t -*-
;; Copyright (C) 2010-2011 Roel van Dijk
;; Author: Roel van Dijk <vandijk.roel@gmail.com>
;; This file is not part of GNU Emacs.
;; 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 of the License, 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 this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;;; Code:
(require 'quail)
;;;###autoload
(defun turn-on-haskell-unicode-input-method ()
"Set input method `haskell-unicode'."
(interactive)
(set-input-method "haskell-unicode"))
(quail-define-package
"haskell-unicode" ;; name
"UTF-8" ;; language
"\\" ;; title
t ;; guidance
"Haskell Unicode input method.
Designed to be used with the Haskell UnicodeSyntax language
extension in combination with the x-unicode-symbols set of
packages (base-unicode-symbols and containers-unicode-symbols).
" ;; docstring
nil ;; translation-keys
nil ;; forget-last-selection
nil ;; deterministic
nil ;; kbd-translate
nil ;; show-layout
nil ;; create-decode-map
nil ;; maximum-shortest
nil ;; overlay-plist
nil ;; update-translation-function
nil ;; conversion-keys
t ;; simple
)
(quail-define-rules
;; Greek letters
("alpha " ["α"])
("Alpha " ["Α"])
("beta " ["β"])
("Beta " ["Β"])
("gamma " ["γ"])
("Gamma " ["Γ"])
("delta " ["δ"])
("Delta " ["Δ"])
("epsilon " ["ε"])
("Epsilon " ["Ε"])
("zeta " ["ζ"])
("Zeta " ["Ζ"])
("eta " ["η"])
("Eta " ["Η"])
("theta " ["θ"])
("Theta " ["Θ"])
("iota " ["ι"])
("Iota " ["Ι"])
("kappa " ["κ"])
("Kappa " ["Κ"])
("lambda " ["λ"])
("Lambda " ["Λ"])
("lamda " ["λ"])
("Lamda " ["Λ"])
("mu " ["μ"])
("Mu " ["Μ"])
("nu " ["ν"])
("Nu " ["Ν"])
("xi " ["ξ"])
("Xi " ["Ξ"])
("omicron " ["ο"])
("Omicron " ["Ο"])
("pi " ["π"])
("Pi " ["Π"])
("rho " ["ρ"])
("Rho " ["Ρ"])
("sigma " ["σ"])
("Sigma " ["Σ"])
("tau " ["τ"])
("Tau " ["Τ"])
("upsilon " ["υ"])
("Upsilon " ["Υ"])
("phi " ["φ"])
("Phi " ["Φ"])
("chi " ["χ"])
("Chi " ["Χ"])
("psi " ["ψ"])
("Psi " ["Ψ"])
("omega " ["ω"])
("Omega " ["Ω"])
("digamma " ["ϝ"])
("Digamma " ["Ϝ"])
("san " ["ϻ"])
("San " ["Ϻ"])
("qoppa " ["ϙ"])
("Qoppa " ["Ϙ"])
("sampi " ["ϡ"])
("Sampi " ["Ϡ"])
("stigma " ["ϛ"])
("Stigma " ["Ϛ"])
("heta " ["ͱ"])
("Heta " ["Ͱ"])
("sho " ["ϸ"])
("Sho " ["Ϸ"])
;; Double-struck letters
("|A|" ["𝔸"])
("|B|" ["𝔹"])
("|C|" [""])
("|D|" ["𝔻"])
("|E|" ["𝔼"])
("|F|" ["𝔽"])
("|G|" ["𝔾"])
("|H|" [""])
("|I|" ["𝕀"])
("|J|" ["𝕁"])
("|K|" ["𝕂"])
("|L|" ["𝕃"])
("|M|" ["𝕄"])
("|N|" [""])
("|O|" ["𝕆"])
("|P|" [""])
("|Q|" [""])
("|R|" [""])
("|S|" ["𝕊"])
("|T|" ["𝕋"])
("|U|" ["𝕌"])
("|V|" ["𝕍"])
("|W|" ["𝕎"])
("|X|" ["𝕏"])
("|Y|" ["𝕐"])
("|Z|" [""])
("|gamma|" [""])
("|Gamma|" [""])
("|pi|" [""])
("|Pi|" [""])
;; Types
("::" [""])
;; Quantifiers
("forall" [""])
("exists" [""])
;; Arrows
("->" [""])
;; ("-->" ["⟶"])
("<-" [""])
;; ("<--" ["⟵"])
;; ("<->" ["↔"])
;; ("<-->" ["⟷"])
("=>" [""])
;; ("==>" ["⟹"])
;; ("<=" ["⇐"])
;; ("<==" ["⟸"])
;; ("<=>" ["⇔"])
;; ("<==>" ["⟺"])
;; ("|->" ["↦"])
;; ("|-->" ["⟼"])
;; ("<-|" ["↤"])
;; ("<--|" ["⟻"])
;; ("|=>" ["⤇"])
;; ("|==>" ["⟾"])
;; ("<=|" ["⤆"])
;; ("<==|" ["⟽"])
("~>" [""])
;; ("~~>" ["⟿"])
("<~" [""])
;; ("<~~" ["⬳"])
;; (">->" ["↣"])
;; ("<-<" ["↢"])
;; ("->>" ["↠"])
;; ("<<-" ["↞"])
;; (">->>" ["⤖"])
;; ("<<-<" ["⬻"])
;; ("<|-" ["⇽"])
;; ("-|>" ["⇾"])
;; ("<|-|>" ["⇿"])
;; ("<-/-" ["↚"])
;; ("-/->" ["↛"])
;; ("<-|-" ["⇷"])
;; ("-|->" ["⇸"])
;; ("<-|->" ["⇹"])
;; ("<-||-" ["⇺"])
;; ("-||->" ["⇻"])
;; ("<-||->" ["⇼"])
;; ("-o->" ["⇴"])
;; ("<-o-" ["⬰"])
;; Boolean operators
;; ("not" ["¬"])
("&&" [""])
("||" [""])
;; Relational operators
("==" [""])
("/=" ["" ""])
("<=" [""])
(">=" [""])
("/<" [""])
("/>" [""])
;; Arithmetic
;; (" / " [" ÷ "])
(" * " [""])
;; Containers / Collections
;; ("++" ["⧺"])
;; ("+++" ["⧻"])
;; ("|||" ["⫴"])
;; ("empty" ["∅"])
("elem" [""])
("notElem" [""])
("member" [""])
("notMember" [""])
("union" [""])
("intersection" [""])
("isSubsetOf" [""])
("isProperSubsetOf" [""])
;; Other
;; ("<<" ["≪"])
;; (">>" ["≫"])
("<<<" [""])
(">>>" [""])
("<|" [""])
("|>" [""])
("><" [""])
;; ("mempty" ["∅"])
("mappend" [""])
;; ("<*>" ["⊛"])
(" . " [""])
("undefined" [""])
(":=" [""])
("=:" [""])
("=def" [""])
("=?" [""])
("..." [""])
;; Braces
;; ("[|" ["〚"])
;; ("|]" ["〛"])
;; Numeric subscripts
("_0 " [""])
("_1 " [""])
("_2 " [""])
("_3 " [""])
("_4 " [""])
("_5 " [""])
("_6 " [""])
("_7 " [""])
("_8 " [""])
("_9 " [""])
;; Numeric superscripts
("^0 " [""])
("^1 " ["¹"])
("^2 " ["²"])
("^3 " ["³"])
("^4 " [""])
("^5 " [""])
("^6 " [""])
("^7 " [""])
("^8 " [""])
("^9 " [""])
)
(provide 'haskell-unicode-input-method)
;;; haskell-unicode-input-method.el ends here

View File

@@ -0,0 +1,193 @@
;;; haskell-utils.el --- General utility functions used by haskell-mode modules -*- lexical-binding: t -*-
;; Copyright © 2013 Herbert Valerio Riedel
;; 2016 Arthur Fayzrakhmanov
;; Author: Herbert Valerio Riedel <hvr@gnu.org>
;; This file is not part of GNU Emacs.
;; 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 of the License, 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 this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; This module's purpose is to provide a place for helper functions
;; which are general enough to be usable by multiple modules and/or
;; to alleviate circular module dependency problems.
;;
;; When possible, functions in this module shall be accompanied by
;; ERT-based unit tests.
;;
;; See also `haskell-str.el' for string utility functions.
;;
;; All symbols in this module have a `haskell-utils-' prefix.
;;; Code:
;; =============================================================================
;; NOTE:
;; THIS MODULE IS SUPPOSED TO BE A LEAF-MODULE AND SHALL NOT REQUIRE/DEPEND-ON
;; ANY OTHER HASKELL-MODE MODULES IN ORDER TO STAY AT THE BOTTOM OF THE MODULE
;; DEPENDENCY GRAPH.
;; =============================================================================
(eval-when-compile (require 'cl-lib))
(defvar-local haskell-utils-async-post-command-flag nil
"Non-nil means some commands were triggered during async function execution.")
(defvar haskell-mode-interactive-prompt-state nil
"Special variable indicating a state of user input waiting.")
(defun haskell-utils-read-directory-name (prompt default)
"Read directory name and normalize to true absolute path.
Refer to `read-directory-name' for the meaning of PROMPT and
DEFAULT. If `haskell-process-load-or-reload-prompt' is nil,
accept `default'."
(let ((filename (file-truename (read-directory-name prompt default default))))
(concat (replace-regexp-in-string "/$" "" filename) "/")))
(defun haskell-utils-parse-import-statement-at-point ()
"Return imported module name if on import statement or nil otherwise.
This currently assumes that the \"import\" keyword and the module
name are on the same line.
This function supports the SafeHaskell and PackageImports syntax extensions.
Note: doesn't detect if in {--}-style comment."
(save-excursion
(goto-char (line-beginning-position))
(if (looking-at (concat "[\t ]*import[\t ]+"
"\\(?:safe[\t ]+\\)?" ;; SafeHaskell
"\\(?:qualified[\t ]+\\)?"
"\\(?:\"[^\"]*\"[\t ]+\\)?" ;; PackageImports
"\\([[:digit:][:upper:][:lower:]_.]+\\)"))
(match-string-no-properties 1))))
(defun haskell-utils-async-update-post-command-flag ()
"A special hook which collects triggered commands during async execution.
This hook pushes value of variable `this-command' to flag variable
`haskell-utils-async-post-command-flag'."
(let* ((cmd this-command)
(updated-flag (cons cmd haskell-utils-async-post-command-flag)))
(setq haskell-utils-async-post-command-flag updated-flag)))
(defun haskell-utils-async-watch-changes ()
"Watch for triggered commands during async operation execution.
Resets flag variable
`haskell-utils-async-update-post-command-flag' to NIL. By changes it is
assumed that nothing happened, e.g. nothing was inserted in
buffer, point was not moved, etc. To collect data `post-command-hook' is used."
(setq haskell-utils-async-post-command-flag nil)
(add-hook
'post-command-hook #'haskell-utils-async-update-post-command-flag nil t))
(defun haskell-utils-async-stop-watching-changes (buffer)
"Clean up after async operation finished.
This function takes care about cleaning up things made by
`haskell-utils-async-watch-changes'. The BUFFER argument is a buffer where
`post-command-hook' should be disabled. This is neccessary, because
it is possible that user will change buffer during async function
execusion."
(with-current-buffer buffer
(setq haskell-utils-async-post-command-flag nil)
(remove-hook
'post-command-hook #'haskell-utils-async-update-post-command-flag t)))
(defun haskell-utils-reduce-string (str)
"Remove newlines and extra whitespace from string STR.
If line starts with a sequence of whitespaces, substitutes this
sequence with a single whitespace. Removes all newline
characters."
(let ((s (replace-regexp-in-string "^\s+" " " str)))
(replace-regexp-in-string "\r?\n" "" s)))
(defun haskell-utils-repl-response-error-status (response)
"Parse response REPL's RESPONSE for errors.
Returns one of the following symbols:
+ unknown-command
+ option-missing
+ interactive-error
+ no-error
*Warning*: this funciton covers only three kind of responses:
* \"unknown command …\"
REPL missing requested command
* \"<interactive>:3:5: …\"
interactive REPL error
* \"Couldn't guess that module name. Does it exist?\"
(:type-at and maybe some other commands error)
* *all other reposnses* are treated as success reposneses and
'no-error is returned."
(if response
(let ((first-line (car (split-string response "\n" t))))
(cond
((null first-line) 'no-error)
((string-match-p "^unknown command" first-line)
'unknown-command)
((string-match-p
"^Couldn't guess that module name. Does it exist?"
first-line)
'option-missing)
((string-match-p "^<interactive>:" first-line)
'interactive-error)
(t 'no-error)))
;; in case of nil-ish reponse it's not clear is it error response or not
'no-error))
(defun haskell-utils-compose-type-at-command (pos)
"Prepare :type-at command to be send to haskell process.
POS is a cons cell containing min and max positions, i.e. target
expression bounds."
(save-excursion
(let ((start-p (car pos))
(end-p (cdr pos))
start-l
start-c
end-l
end-c
value)
(goto-char start-p)
(setq start-l (line-number-at-pos))
(setq start-c (1+ (current-column)))
(goto-char end-p)
(setq end-l (line-number-at-pos))
(setq end-c (1+ (current-column)))
(setq value (buffer-substring-no-properties start-p end-p))
;; supress multiline expressions
(let ((lines (split-string value "\n" t)))
(when (and (cdr lines)
(stringp (car lines)))
(setq value (format "[ %s … ]" (car lines)))))
(replace-regexp-in-string
"\n$"
""
(format ":type-at %s %d %d %d %d %s"
(buffer-file-name)
start-l
start-c
end-l
end-c
value)))))
(defun haskell-mode-toggle-interactive-prompt-state (&optional disabled)
"Set `haskell-mode-interactive-prompt-state' to t.
If given DISABLED argument sets variable value to nil, otherwise to t."
(setq haskell-mode-interactive-prompt-state (not disabled)))
(provide 'haskell-utils)
;;; haskell-utils.el ends here

Binary file not shown.

View File

@@ -0,0 +1,528 @@
;;; haskell.el --- Top-level Haskell package -*- lexical-binding: t -*-
;; Copyright © 2014 Chris Done. All rights reserved.
;; 2016 Arthur Fayzrakhmanov
;; 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 this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;;; Code:
(require 'cl-lib)
(require 'haskell-mode)
(require 'haskell-hoogle)
(require 'haskell-process)
(require 'haskell-debug)
(require 'haskell-interactive-mode)
(require 'haskell-repl)
(require 'haskell-load)
(require 'haskell-commands)
(require 'haskell-modules)
(require 'haskell-string)
(require 'haskell-completions)
(require 'haskell-utils)
(require 'haskell-customize)
(defvar interactive-haskell-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "C-c C-l") 'haskell-process-load-file)
(define-key map (kbd "C-c C-r") 'haskell-process-reload)
(define-key map (kbd "C-c C-t") 'haskell-process-do-type)
(define-key map (kbd "C-c C-i") 'haskell-process-do-info)
(define-key map (kbd "M-.") 'haskell-mode-jump-to-def-or-tag)
(define-key map (kbd "C-c C-k") 'haskell-interactive-mode-clear)
(define-key map (kbd "C-c C-c") 'haskell-process-cabal-build)
(define-key map (kbd "C-c v c") 'haskell-cabal-visit-file)
(define-key map (kbd "C-c C-x") 'haskell-process-cabal)
(define-key map (kbd "C-c C-b") 'haskell-interactive-switch)
(define-key map (kbd "C-c C-z") 'haskell-interactive-switch)
map)
"Keymap for using `interactive-haskell-mode'.")
;;;###autoload
(define-minor-mode interactive-haskell-mode
"Minor mode for enabling haskell-process interaction."
:lighter " Interactive"
:keymap interactive-haskell-mode-map
(add-hook 'completion-at-point-functions
#'haskell-completions-sync-repl-completion-at-point
nil
t))
(make-obsolete 'haskell-process-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."
(when (haskell-session-maybe)
(let ((process (haskell-process))
symbol-bounds)
(cond
;; ghci can complete module names, but it needs the "import "
;; string at the beginning
((looking-back (rx line-start
"import" (1+ space)
(? "qualified" (1+ space))
(group (? (char upper) ; modid
(* (char alnum ?' ?.)))))
(line-beginning-position))
(let ((text (match-string-no-properties 0))
(start (match-beginning 1))
(end (match-end 1)))
(list start end
(haskell-process-get-repl-completions process text))))
;; Complete OPTIONS, a completion list comes from variable
;; `haskell-ghc-supported-options'
((and (nth 4 (syntax-ppss))
(save-excursion
(let ((p (point)))
(and (search-backward "{-#" nil t)
(search-forward-regexp "\\_<OPTIONS\\(?:_GHC\\)?\\_>" p t))))
(looking-back
(rx symbol-start "-" (* (char alnum ?-)))
(line-beginning-position)))
(list (match-beginning 0) (match-end 0) haskell-ghc-supported-options))
;; Complete LANGUAGE, a list of completions comes from variable
;; `haskell-ghc-supported-extensions'
((and (nth 4 (syntax-ppss))
(save-excursion
(let ((p (point)))
(and (search-backward "{-#" nil t)
(search-forward-regexp "\\_<LANGUAGE\\_>" p t))))
(setq symbol-bounds (bounds-of-thing-at-point 'symbol)))
(list (car symbol-bounds) (cdr symbol-bounds)
haskell-ghc-supported-extensions))
((setq symbol-bounds (haskell-ident-pos-at-point))
(cl-destructuring-bind (start . end) symbol-bounds
(list start end
(haskell-process-get-repl-completions
process (buffer-substring-no-properties start end)))))))))
;;;###autoload
(defun haskell-interactive-mode-return ()
"Handle the return key."
(interactive)
(cond
;; At a compile message, jump to the location of the error in the
;; source.
((haskell-interactive-at-compile-message)
(next-error-internal))
;; At the input prompt, handle the expression in the usual way.
((haskell-interactive-at-prompt)
(haskell-interactive-handle-expr))
;; At any other location in the buffer, copy the line to the
;; current prompt.
(t
(haskell-interactive-copy-to-prompt))))
;;;###autoload
(defun haskell-session-kill (&optional leave-interactive-buffer)
"Kill the session process and buffer, delete the session.
0. Prompt to kill all associated buffers.
1. Kill the process.
2. Kill the interactive buffer unless LEAVE-INTERACTIVE-BUFFER is not given.
3. Walk through all the related buffers and set their haskell-session to nil.
4. Remove the session from the sessions list."
(interactive)
(haskell-mode-toggle-interactive-prompt-state)
(unwind-protect
(let* ((session (haskell-session))
(name (haskell-session-name session))
(also-kill-buffers
(and haskell-ask-also-kill-buffers
(y-or-n-p
(format "Killing `%s'. Also kill all associated buffers?"
name)))))
(haskell-kill-session-process session)
(unless leave-interactive-buffer
(kill-buffer (haskell-session-interactive-buffer session)))
(cl-loop for buffer in (buffer-list)
do (with-current-buffer buffer
(when (and (boundp 'haskell-session)
(string= (haskell-session-name haskell-session)
name))
(setq haskell-session nil)
(when also-kill-buffers
(kill-buffer)))))
(setq haskell-sessions
(cl-remove-if (lambda (session)
(string= (haskell-session-name session)
name))
haskell-sessions)))
(haskell-mode-toggle-interactive-prompt-state t)))
;;;###autoload
(defun haskell-interactive-kill ()
"Kill the buffer and (maybe) the session."
(interactive)
(when (eq major-mode 'haskell-interactive-mode)
(haskell-mode-toggle-interactive-prompt-state)
(unwind-protect
(when (and (boundp 'haskell-session)
haskell-session
(y-or-n-p "Kill the whole session?"))
(haskell-session-kill t)))
(haskell-mode-toggle-interactive-prompt-state t)))
(defun haskell-session-make (name)
"Make a Haskell session."
(when (haskell-session-lookup name)
(error "Session of name %s already exists!" name))
(let ((session (setq haskell-session
(list (cons 'name name)))))
(add-to-list 'haskell-sessions session)
(haskell-process-start session)
session))
(defun haskell-session-new-assume-from-cabal ()
"Prompt to create a new project based on a guess from the nearest Cabal file.
If `haskell-process-load-or-reload-prompt' is nil, accept `default'."
(let ((name (haskell-session-default-name)))
(unless (haskell-session-lookup name)
(haskell-mode-toggle-interactive-prompt-state)
(unwind-protect
(if (or (not haskell-process-load-or-reload-prompt)
(y-or-n-p (format "Start a new project named “%s”? " name)))
(haskell-session-make name))
(haskell-mode-toggle-interactive-prompt-state t)))))
;;;###autoload
(defun haskell-session ()
"Get the Haskell session, prompt if there isn't one or fail."
(or (haskell-session-maybe)
(haskell-session-assign
(or (haskell-session-from-buffer)
(haskell-session-new-assume-from-cabal)
(haskell-session-choose)
(haskell-session-new)))))
;;;###autoload
(defun haskell-interactive-switch ()
"Switch to the interactive mode for this session."
(interactive)
(let ((initial-buffer (current-buffer))
(buffer (haskell-session-interactive-buffer (haskell-session))))
(with-current-buffer buffer
(setq haskell-interactive-previous-buffer initial-buffer))
(unless (eq buffer (window-buffer))
(switch-to-buffer-other-window buffer))))
(defun haskell-session-new ()
"Make a new session."
(let ((name (read-from-minibuffer "Project name: " (haskell-session-default-name))))
(when (not (string= name ""))
(let ((session (haskell-session-lookup name)))
(haskell-mode-toggle-interactive-prompt-state)
(unwind-protect
(if session
(when
(y-or-n-p
(format "Session %s already exists. Use it?" name))
session)
(haskell-session-make name)))
(haskell-mode-toggle-interactive-prompt-state t)))))
;;;###autoload
(defun haskell-session-change ()
"Change the session for the current buffer."
(interactive)
(haskell-session-assign (or (haskell-session-new-assume-from-cabal)
(haskell-session-choose)
(haskell-session-new))))
(defun haskell-process-prompt-restart (process)
"Prompt to restart the died PROCESS."
(let ((process-name (haskell-process-name process))
(cursor-in-echo-area t))
(if haskell-process-suggest-restart
(progn
(haskell-mode-toggle-interactive-prompt-state)
(unwind-protect
(cond
((string-match "You need to re-run the 'configure' command."
(haskell-process-response process))
(cl-case (read-char-choice
(concat
"The Haskell process ended. Cabal wants you to run "
(propertize "cabal configure"
'face
'font-lock-keyword-face)
" because there is a version mismatch. Re-configure (y, n, l: view log)?"
"\n\n"
"Cabal said:\n\n"
(propertize (haskell-process-response process)
'face
'font-lock-comment-face))
'(?l ?n ?y))
(?y (let ((default-directory
(haskell-session-cabal-dir
(haskell-process-session process))))
(message "%s"
(shell-command-to-string "cabal configure"))))
(?l (let* ((response (haskell-process-response process))
(buffer (get-buffer "*haskell-process-log*")))
(if buffer
(switch-to-buffer buffer)
(progn (switch-to-buffer
(get-buffer-create "*haskell-process-log*"))
(insert response)))))
(?n)))
(t
(cl-case (read-char-choice
(propertize
(format "The Haskell process `%s' has died. Restart? (y, n, l: show process log) "
process-name)
'face
'minibuffer-prompt)
'(?l ?n ?y))
(?y (haskell-process-start (haskell-process-session process)))
(?l (let* ((response (haskell-process-response process))
(buffer (get-buffer "*haskell-process-log*")))
(if buffer
(switch-to-buffer buffer)
(progn (switch-to-buffer
(get-buffer-create "*haskell-process-log*"))
(insert response)))))
(?n))))
;; unwind
(haskell-mode-toggle-interactive-prompt-state t)))
(message "The Haskell process `%s' is dearly departed." process-name))))
(defun haskell-process ()
"Get the current process from the current session."
(haskell-session-process (haskell-session)))
;;;###autoload
(defun haskell-kill-session-process (&optional session)
"Kill the process."
(interactive)
(let* ((session (or session (haskell-session)))
(existing-process (get-process (haskell-session-name session))))
(when (processp existing-process)
(haskell-interactive-mode-echo session "Killing process ...")
(haskell-process-set (haskell-session-process session) 'is-restarting t)
(delete-process existing-process))))
;;;###autoload
(defun haskell-interactive-mode-visit-error ()
"Visit the buffer of the current (or last) error message."
(interactive)
(with-current-buffer (haskell-session-interactive-buffer (haskell-session))
(if (progn (goto-char (line-beginning-position))
(looking-at haskell-interactive-mode-error-regexp))
(progn (forward-line -1)
(haskell-interactive-jump-to-error-line))
(progn (goto-char (point-max))
(haskell-interactive-mode-error-backward)
(haskell-interactive-jump-to-error-line)))))
(defvar xref-prompt-for-identifier nil)
;;;###autoload
(defun haskell-mode-jump-to-tag (&optional next-p)
"Jump to the tag of the given identifier.
Give optional NEXT-P parameter to override value of
`xref-prompt-for-identifier' during definition search."
(interactive "P")
(let ((ident (haskell-string-drop-qualifier (haskell-ident-at-point)))
(tags-file-dir (haskell-cabal--find-tags-dir))
(tags-revert-without-query t))
(when (and ident
(not (string= "" (haskell-string-trim ident)))
tags-file-dir)
(let ((tags-file-name (concat tags-file-dir "TAGS")))
(cond ((file-exists-p tags-file-name)
(let ((xref-prompt-for-identifier next-p))
(xref-find-definitions ident)))
(t (haskell-mode-generate-tags ident)))))))
;;;###autoload
(defun haskell-mode-after-save-handler ()
"Function that will be called after buffer's saving."
(when haskell-tags-on-save
(ignore-errors (haskell-mode-generate-tags))))
;;;###autoload
(defun haskell-mode-tag-find (&optional _next-p)
"The tag find function, specific for the particular session."
(interactive "P")
(cond
((elt (syntax-ppss) 3) ;; Inside a string
(haskell-mode-jump-to-filename-in-string))
(t (call-interactively 'haskell-mode-jump-to-tag))))
(defun haskell-mode-jump-to-filename-in-string ()
"Jump to the filename in the current string."
(let* ((string (save-excursion
(buffer-substring-no-properties
(1+ (search-backward-regexp "\"" (line-beginning-position) nil 1))
(1- (progn (forward-char 1)
(search-forward-regexp "\"" (line-end-position) nil 1))))))
(fp (expand-file-name string
(haskell-session-cabal-dir (haskell-session)))))
(find-file
(read-file-name
""
fp
fp))))
;;;###autoload
(defun haskell-interactive-bring ()
"Bring up the interactive mode for this session."
(interactive)
(let* ((session (haskell-session))
(buffer (haskell-session-interactive-buffer session)))
(pop-to-buffer buffer)))
;;;###autoload
(defun haskell-process-load-file ()
"Load the current buffer file."
(interactive)
(save-buffer)
(haskell-interactive-mode-reset-error (haskell-session))
(haskell-process-file-loadish (format "load \"%s\"" (replace-regexp-in-string
"\""
"\\\\\""
(buffer-file-name)))
nil
(current-buffer)))
;;;###autoload
(defun haskell-process-reload ()
"Re-load the current buffer file."
(interactive)
(save-buffer)
(haskell-interactive-mode-reset-error (haskell-session))
(haskell-process-file-loadish "reload" t (current-buffer)))
;;;###autoload
(defun haskell-process-reload-file () (haskell-process-reload))
(make-obsolete 'haskell-process-reload-file 'haskell-process-reload
"2015-11-14")
;;;###autoload
(defun haskell-process-load-or-reload (&optional toggle)
"Load or reload. Universal argument toggles which."
(interactive "P")
(if toggle
(progn (setq haskell-reload-p (not haskell-reload-p))
(message "%s (No action taken this time)"
(if haskell-reload-p
"Now running :reload."
"Now running :load <buffer-filename>.")))
(if haskell-reload-p (haskell-process-reload) (haskell-process-load-file))))
(make-obsolete 'haskell-process-load-or-reload 'haskell-process-load-file
"2015-11-14")
;;;###autoload
(defun haskell-process-cabal-build ()
"Build the Cabal project."
(interactive)
(haskell-process-do-cabal "build")
(haskell-process-add-cabal-autogen))
;;;###autoload
(defun haskell-process-cabal (p)
"Prompts for a Cabal command to run."
(interactive "P")
(if p
(haskell-process-do-cabal
(read-from-minibuffer "Cabal command (e.g. install): "))
(haskell-process-do-cabal
(funcall haskell-completing-read-function "Cabal command: "
(append haskell-cabal-commands
(list "build --ghc-options=-fforce-recomp"))))))
(defun haskell-process-file-loadish (command reload-p module-buffer)
"Run a loading-ish COMMAND that wants to pick up type errors\
and things like that. RELOAD-P indicates whether the notification
should say 'reloaded' or 'loaded'. MODULE-BUFFER may be used
for various things, but is optional."
(let ((session (haskell-session)))
(haskell-session-current-dir session)
(when haskell-process-check-cabal-config-on-load
(haskell-process-look-config-changes session))
(let ((process (haskell-process)))
(haskell-process-queue-command
process
(make-haskell-command
:state (list session process command reload-p module-buffer)
:go (lambda (state)
(haskell-process-send-string
(cadr state) (format ":%s" (cl-caddr state))))
:live (lambda (state buffer)
(haskell-process-live-build
(cadr state) buffer nil))
:complete (lambda (state response)
(haskell-process-load-complete
(car state)
(cadr state)
response
(cl-cadddr state)
(cl-cadddr (cdr state)))))))))
;;;###autoload
(defun haskell-process-minimal-imports ()
"Dump minimal imports."
(interactive)
(unless (> (save-excursion
(goto-char (point-min))
(haskell-navigate-imports-go)
(point))
(point))
(goto-char (point-min))
(haskell-navigate-imports-go))
(haskell-process-queue-sync-request (haskell-process)
":set -ddump-minimal-imports")
(haskell-process-load-file)
(insert-file-contents-literally
(concat (haskell-session-current-dir (haskell-session))
"/"
(haskell-guess-module-name-from-file-name (buffer-file-name))
".imports")))
(defun haskell-interactive-jump-to-error-line ()
"Jump to the error line."
(let ((orig-line (buffer-substring-no-properties (line-beginning-position)
(line-end-position))))
(and (string-match "^\\([^:]+\\):\\([0-9]+\\):\\([0-9]+\\)\\(-[0-9]+\\)?:" orig-line)
(let* ((file (match-string 1 orig-line))
(line (match-string 2 orig-line))
(col (match-string 3 orig-line))
(session (haskell-interactive-session))
(cabal-path (haskell-session-cabal-dir session))
(src-path (haskell-session-current-dir session))
(cabal-relative-file (expand-file-name file cabal-path))
(src-relative-file (expand-file-name file src-path)))
(let ((file (cond ((file-exists-p cabal-relative-file)
cabal-relative-file)
((file-exists-p src-relative-file)
src-relative-file))))
(when file
(other-window 1)
(find-file file)
(haskell-interactive-bring)
(goto-char (point-min))
(forward-line (1- (string-to-number line)))
(goto-char (+ (point) (string-to-number col) -1))
(haskell-mode-message-line orig-line)
t))))))
(provide 'haskell)
;;; haskell.el ends here

Binary file not shown.

View File

@@ -0,0 +1,106 @@
;;; highlight-uses-mode.el --- Mode for highlighting uses -*- lexical-binding: t -*-
;; Copyright (c) 2014 Chris Done. All rights reserved.
;; 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 this program. If not, see <http://www.gnu.org/licenses/>.
;;; Code:
(require 'cl-lib)
(defvar highlight-uses-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "TAB") 'highlight-uses-mode-next)
(define-key map (kbd "S-TAB") 'highlight-uses-mode-prev)
(define-key map (kbd "<backtab>") 'highlight-uses-mode-prev)
(define-key map (kbd "RET") 'highlight-uses-mode-stop-here)
(define-key map (kbd "C-g") 'highlight-uses-mode)
map)
"Keymap for using `highlight-uses-mode'.")
(defvar-local highlight-uses-mode-point nil)
;;;###autoload
(define-minor-mode highlight-uses-mode
"Minor mode for highlighting and jumping between uses."
:lighter " Uses"
:keymap highlight-uses-mode-map
(if highlight-uses-mode
(setq highlight-uses-mode-point (point))
(when highlight-uses-mode-point
(goto-char highlight-uses-mode-point)))
(remove-overlays (point-min) (point-max) 'highlight-uses-mode-highlight t))
(defun highlight-uses-mode-replace ()
"Replace all highlighted instances in the buffer with something
else."
(interactive)
(save-excursion
(goto-char (point-min))
(let ((o (highlight-uses-mode-next)))
(when o
(let ((replacement (read-from-minibuffer (format "Replace uses %s with: "
(buffer-substring
(overlay-start o)
(overlay-end o))))))
(while o
(goto-char (overlay-start o))
(delete-region (overlay-start o)
(overlay-end o))
(insert replacement)
(setq o (highlight-uses-mode-next))))))))
(defun highlight-uses-mode-stop-here ()
"Stop at this point."
(interactive)
(setq highlight-uses-mode-point (point))
(highlight-uses-mode -1))
(defun highlight-uses-mode-next ()
"Jump to next result."
(interactive)
(let ((os (sort (cl-remove-if (lambda (o)
(or (<= (overlay-start o) (point))
(not (overlay-get o 'highlight-uses-mode-highlight))))
(overlays-in (point) (point-max)))
(lambda (a b)
(< (overlay-start a)
(overlay-start b))))))
(when os
(goto-char (overlay-start (car os)))
(car os))))
(defun highlight-uses-mode-prev ()
"Jump to previous result."
(interactive)
(let ((os (sort (cl-remove-if (lambda (o)
(or (>= (overlay-end o) (point))
(not (overlay-get o 'highlight-uses-mode-highlight))))
(overlays-in (point-min) (point)))
(lambda (a b)
(> (overlay-start a)
(overlay-start b))))))
(when os
(goto-char (overlay-start (car os)))
(car os))))
(defun highlight-uses-mode-highlight (start end)
"Make a highlight overlay at the given span."
(let ((o (make-overlay start end)))
(overlay-put o 'priority 999)
(overlay-put o 'face 'isearch)
(overlay-put o 'highlight-uses-mode-highlight t)))
(provide 'highlight-uses-mode)

Binary file not shown.

View File

@@ -0,0 +1,262 @@
;;; inf-haskell.el --- Interaction with an inferior Haskell process -*- lexical-binding: t -*-
;; Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, Inc.
;; Copyright (C) 2017 Vasantha Ganesh Kanniappan <vasanthaganesh.k@tuta.io>
;; Author: Stefan Monnier <monnier@iro.umontreal.ca>
;; Keywords: Haskell
;; 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 this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; A major mode for the buffer that holds the inferior process
;; Todo:
;; - Check out Shim for ideas.
;; - i-h-load-buffer and i-h-send-region.
;;; Code:
(require 'comint)
(require 'shell) ; For directory tracking.
(require 'etags)
(require 'haskell-compat)
(require 'compile)
(require 'haskell-decl-scan)
(require 'haskell-cabal)
(require 'haskell-customize)
(require 'cl-lib)
(require 'haskell-string)
;;;###autoload
(defgroup inferior-haskell nil
"Settings for REPL interaction via `inferior-haskell-mode'"
:link '(custom-manual "(haskell-mode)inferior-haskell-mode")
:prefix "inferior-haskell-"
:prefix "haskell-"
:group 'haskell)
(defcustom inferior-haskell-hook nil
"The hook that is called after starting inf-haskell."
:type 'hook)
(defun haskell-program-name-with-args ()
"Return the command with the arguments to start the repl based on the
directory structure."
(cl-ecase (haskell-process-type)
('ghci (cond ((eq system-type 'cygwin) (nconc "ghcii.sh"
haskell-process-args-ghci))
(t (nconc `(,haskell-process-path-ghci)
haskell-process-args-ghci))))
('cabal-repl (nconc `(,haskell-process-path-cabal
"repl")
haskell-process-args-cabal-repl))
('stack-ghci (nconc `(,haskell-process-path-stack
"ghci")
haskell-process-args-stack-ghci))))
(defconst inferior-haskell-info-xref-re
"-- Defined at \\(.+\\):\\([0-9]+\\):\\([0-9]+\\)\\(?:-\\([0-9]+\\)\\)?$")
(defconst inferior-haskell-module-re
"-- Defined in \\(.+\\)$"
"Regular expression for matching module names in :info.")
(defvar inferior-haskell-multiline-prompt-re
"^\\*?[[:upper:]][\\._[:alnum:]]*\\(?: \\*?[[:upper:]][\\._[:alnum:]]*\\)*| "
"Regular expression for matching multiline prompt (the one inside :{ ... :} blocks).")
(defconst inferior-haskell-error-regexp-alist
`(;; Format of error messages used by GHCi.
("^\\(.+?\\):\\([0-9]+\\):\\(\\([0-9]+\\):\\)?\\( \\|\n *\\)\\([Ww]arning\\)?"
1 2 4 ,@(if (fboundp 'compilation-fake-loc)
'((6) nil (5 '(face nil font-lock-multiline t)))))
;; Runtime exceptions, from ghci.
("^\\*\\*\\* Exception: \\(.+?\\):(\\([0-9]+\\),\\([0-9]+\\))-(\\([0-9]+\\),\\([0-9]+\\)): .*"
1 ,@(if (fboundp 'compilation-fake-loc) '((2 . 4) (3 . 5)) '(2 3)))
;; GHCi uses two different forms for line/col ranges, depending on
;; whether it's all on the same line or not :-( In Emacs-23, I could use
;; explicitly numbered subgroups to merge the two patterns.
("^\\*\\*\\* Exception: \\(.+?\\):\\([0-9]+\\):\\([0-9]+\\)-\\([0-9]+\\): .*"
1 2 ,(if (fboundp 'compilation-fake-loc) '(3 . 4) 3))
;; Info messages. Not errors per se.
,@(when (fboundp 'compilation-fake-loc)
`(;; Other GHCi patterns used in type errors.
("^[ \t]+at \\(.+\\):\\([0-9]+\\):\\([0-9]+\\)-\\([0-9]+\\)$"
1 2 (3 . 4) 0)
;; Foo.hs:318:80:
;; Ambiguous occurrence `Bar'
;; It could refer to either `Bar', defined at Zork.hs:311:5
;; or `Bar', imported from Bars at Frob.hs:32:0-16
;; (defined at Location.hs:97:5)
("[ (]defined at \\(.+\\):\\([0-9]+\\):\\([0-9]+\\))?$" 1 2 3 0)
("imported from .* at \\(.+\\):\\([0-9]+\\):\\([0-9]+\\)-\\([0-9]+\\)$"
1 2 (3 . 4) 0)
;; Info xrefs.
(,inferior-haskell-info-xref-re 1 2 (3 . 4) 0))))
"Regexps for error messages generated by inferior Haskell processes.
The format should be the same as for `compilation-error-regexp-alist'.")
(defconst haskell-prompt-regexp
;; Why the backslash in [\\._[:alnum:]]?
"^\\*?[[:upper:]][\\._[:alnum:]]*\\(?: \\*?[[:upper:]][\\._[:alnum:]]*\\)*\\( λ\\)?> \\|^λ?> $")
;;; TODO
;;; -> Make font lock work for strings, directories, hyperlinks
;;; -> Make font lock work for key words???
(defvar inf-haskell-map
(let ((map (make-sparse-keymap)))
(define-key map "\C-c\C-d" 'comint-kill-subjob)
map))
(defvaralias 'inferior-haskell-mode-map 'inf-haskell-map)
(define-derived-mode inferior-haskell-mode comint-mode "Inf-Haskell"
"Major mode for interacting with an inferior Haskell process."
:group 'inferior-haskell
(setq-local comint-prompt-regexp haskell-prompt-regexp)
(setq-local paragraph-start haskell-prompt-regexp)
(setq-local comint-input-autoexpand nil)
(setq-local comint-prompt-read-only t)
;; Setup directory tracking.
(setq-local shell-cd-regexp ":cd")
(condition-case nil
(shell-dirtrack-mode 1)
(error ;The minor mode function may not exist or not accept an arg.
(setq-local shell-dirtrackp t)
(add-hook 'comint-input-filter-functions 'shell-directory-tracker
nil 'local)))
;; Setup `compile' support so you can just use C-x ` and friends.
(setq-local compilation-error-regexp-alist inferior-haskell-error-regexp-alist)
(setq-local compilation-first-column 0) ;GHCI counts from 0.
(if (and (not (boundp 'minor-mode-overriding-map-alist))
(fboundp 'compilation-shell-minor-mode))
;; If we can't remove compilation-minor-mode bindings, at least try to
;; use compilation-shell-minor-mode, so there are fewer
;; annoying bindings.
(compilation-shell-minor-mode 1)
;; Else just use compilation-minor-mode but without its bindings because
;; things like mouse-2 are simply too annoying.
(compilation-minor-mode 1)
(let ((map (make-sparse-keymap)))
(dolist (keys '([menu-bar] [follow-link]))
;; Preserve some of the bindings.
(define-key map keys (lookup-key compilation-minor-mode-map keys)))
(add-to-list 'minor-mode-overriding-map-alist
(cons 'compilation-minor-mode map))))
(add-hook 'inferior-haskell-hook 'inferior-haskell-init))
(defvar inferior-haskell-buffer nil
"The buffer in which the inferior process is running.")
(defun inferior-haskell-start-process ()
"Start an inferior haskell process.
With universal prefix \\[universal-argument], prompts for a COMMAND,
otherwise uses `haskell-program-name-with-args'.
It runs the hook `inferior-haskell-hook' after starting the process and
setting up the inferior-haskell buffer."
(let ((command (haskell-program-name-with-args)))
(setq default-directory inferior-haskell-root-dir)
(setq inferior-haskell-buffer
(apply 'make-comint "haskell" (car command) nil (cdr command)))
(with-current-buffer inferior-haskell-buffer
(inferior-haskell-mode)
(run-hooks 'inferior-haskell-hook))))
(defun inferior-haskell-process ()
"Restart if not present."
(cond ((and (buffer-live-p inferior-haskell-buffer)
(comint-check-proc inferior-haskell-buffer))
(get-buffer-process inferior-haskell-buffer))
(t (inferior-haskell-start-process)
(inferior-haskell-process))))
;;;###autoload
(defalias 'run-haskell 'switch-to-haskell)
;;;###autoload
(defun switch-to-haskell ()
"Show the inferior-haskell buffer. Start the process if needed."
(interactive)
(let ((proc (inferior-haskell-process)))
(pop-to-buffer-same-window (process-buffer proc))))
(defvar inferior-haskell-result-history nil)
(defvar haskell-next-input ""
"This is a temporary variable to store the intermediate results while
`accecpt-process-output' with `haskell-extract-exp'")
(defun haskell-extract-exp (str)
(setq haskell-next-input (concat haskell-next-input str))
(if (with-temp-buffer
(insert haskell-next-input)
(re-search-backward haskell-prompt-regexp nil t 1))
(progn
(push (substring haskell-next-input
0
(1- (with-temp-buffer
(insert haskell-next-input)
(re-search-backward haskell-prompt-regexp nil t 1))))
inferior-haskell-result-history)
(setq haskell-next-input ""))
""))
(defun inferior-haskell-no-result-return (strg)
(let ((proc (inferior-haskell-process)))
(with-local-quit
(progn
(add-to-list 'comint-preoutput-filter-functions
(lambda (output)
(haskell-extract-exp output)))
(process-send-string proc strg)
(accept-process-output proc)
(sit-for 0.1)
(setq comint-preoutput-filter-functions nil)))))
(defun inferior-haskell-get-result (inf-expr)
"Submit the expression `inf-expr' to ghci and read the result."
(let* ((times 5))
(inferior-haskell-no-result-return (concat inf-expr "\n"))
(while (and (> times 0)
(not (stringp (car inferior-haskell-result-history))))
(setq times (1- times))
(inferior-haskell-no-result-return (concat inf-expr "\n")))
(haskell-string-chomp (car inferior-haskell-result-history))))
(defun inferior-haskell-init ()
"The first thing run while initalizing inferior-haskell-buffer"
(with-local-quit
(with-current-buffer inferior-haskell-buffer
(process-send-string (inferior-haskell-process) "\n")
(accept-process-output (inferior-haskell-process))
(sit-for 0.1))))
(defvar haskell-set+c-p nil
"t if `:set +c` else nil")
(defun haskell-set+c ()
"set `:set +c` is not already set"
(if (not haskell-set+c-p)
(inferior-haskell-get-result ":set +c")))
(provide 'inf-haskell)
;;; inf-haskell.el ends here

Binary file not shown.

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="481.8897pt" height="340.1574pt" viewBox="0 0 481.8897 340.1574" version="1.1">
<defs>
<clipPath id="clip1">
<path d="M 0 340.15625 L 481.890625 340.15625 L 481.890625 0 L 0 0 L 0 340.15625 Z M 0 340.15625 "/>
</clipPath>
</defs>
<g id="surface0">
<g clip-path="url(#clip1)" clip-rule="nonzero">
<path style=" stroke:none;fill-rule: nonzero; fill: rgb(40%,40%,40%); fill-opacity: 1;" d="M 0 340.15625 L 113.386719 170.078125 L 0 0 L 85.039062 0 L 198.425781 170.078125 L 85.039062 340.15625 L 0 340.15625 Z M 0 340.15625 "/>
<path style=" stroke:none;fill-rule: nonzero; fill: rgb(60%,60%,60%); fill-opacity: 1;" d="M 113.386719 340.15625 L 226.773438 170.078125 L 113.386719 0 L 198.425781 0 L 425.195312 340.15625 L 340.15625 340.15625 L 269.292969 233.859375 L 198.425781 340.15625 L 113.386719 340.15625 Z M 113.386719 340.15625 "/>
<path style=" stroke:none;fill-rule: nonzero; fill: rgb(40%,40%,40%); fill-opacity: 1;" d="M 387.402344 240.945312 L 349.609375 184.253906 L 481.890625 184.25 L 481.890625 240.945312 L 387.402344 240.945312 Z M 387.402344 240.945312 "/>
<path style=" stroke:none;fill-rule: nonzero; fill: rgb(40%,40%,40%); fill-opacity: 1;" d="M 330.710938 155.90625 L 292.914062 99.214844 L 481.890625 99.210938 L 481.890625 155.90625 L 330.710938 155.90625 Z M 330.710938 155.90625 "/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,190 @@
;;; -*- lexical-binding: t -*-
;;; w3m-haddock.el --- Make browsing haddocks with w3m-mode better.
;; Copyright (C) 2014 Chris Done
;; Author: Chris Done <chrisdone@gmail.com>
;; This file is not part of GNU Emacs.
;; 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.
(require 'cl-lib)
(require 'haskell-mode)
(require 'haskell-font-lock)
(declare-function w3m-buffer-title "ext:w3m")
(declare-function w3m-browse-url "ext:w3m")
(defvar w3m-current-url)
(add-hook 'w3m-display-hook 'w3m-haddock-display)
;;;###autoload
(defface w3m-haddock-heading-face
'((((class color)) :inherit highlight))
"Face for quarantines."
:group 'haskell)
(defcustom haskell-w3m-haddock-dirs
'("~/.cabal/share/doc/")
"The path to your cabal documentation dir. It should contain
directories of package-name-x.x.
You can rebind this if you're using hsenv by adding it to your
.dir-locals.el in your project root. E.g.
((haskell-mode . ((haskell-w3m-haddock-dirs . (\"/home/chris/Projects/foobar/.hsenv/cabal/share/doc\")))))
"
:group 'haskell
:type 'list)
(defvar w3m-haddock-entry-regex "^\\(\\(data\\|type\\) \\|[a-z].* :: \\)"
"Regex to match entry headings.")
(defun haskell-w3m-open-haddock ()
"Open a haddock page in w3m."
(interactive)
(let* ((entries (cl-remove-if (lambda (s) (string= s ""))
(apply 'append (mapcar (lambda (dir)
(split-string (shell-command-to-string (concat "ls -1 " dir))
"\n"))
haskell-w3m-haddock-dirs))))
(package-dir (ido-completing-read
"Package: "
entries)))
(cond
((member package-dir entries)
(unless (cl-loop for dir in haskell-w3m-haddock-dirs
when (w3m-haddock-find-index dir package-dir)
do (progn (w3m-browse-url (w3m-haddock-find-index dir package-dir)
t)
(cl-return t)))
(w3m-browse-url (concat "http://hackage.haskell.org/package/"
package-dir)
t)))
(t
(w3m-browse-url (concat "http://hackage.haskell.org/package/"
package-dir)
t)))))
(defun w3m-haddock-find-index (dir package)
(let ((html-index (concat dir "/" package "/html/index.html"))
(index (concat dir "/" package "/index.html")))
(cond
((file-exists-p html-index)
html-index)
((file-exists-p index)
index))))
(defun w3m-haddock-page-p ()
"Haddock general page?"
(save-excursion
(goto-char (point-max))
(forward-line -2)
(looking-at "[ ]*Produced by Haddock")))
(defun w3m-haddock-source-p ()
"Haddock source page?"
(save-excursion
(goto-char (point-min))
(or (looking-at "Location: https?://hackage.haskell.org/package/.*/docs/src/")
(looking-at "Location: file://.*cabal/share/doc/.*/html/src/")
(looking-at "Location: .*src/.*.html$"))))
(defun w3m-haddock-p ()
"Any haddock page?"
(or (w3m-haddock-page-p)
(w3m-haddock-source-p)))
(defun w3m-haddock-find-tag ()
"Find a tag by jumping to the \"All\" index and doing a
search-forward."
(interactive)
(when (w3m-haddock-p)
(let ((ident (haskell-ident-at-point)))
(when ident
(w3m-browse-url
(replace-regexp-in-string "docs/.*" "docs/doc-index-All.html" w3m-current-url))
(search-forward ident)))))
(defun w3m-haddock-display (_url)
"To be run by w3m's display hook. This takes a normal w3m
buffer containing hadddock documentation and reformats it to be
more usable and look like a dedicated documentation page."
(when (w3m-haddock-page-p)
(save-excursion
(goto-char (point-min))
(let ((inhibit-read-only t))
(delete-region (point)
(line-end-position))
(w3m-haddock-next-heading)
;; Start formatting entries
(while (looking-at w3m-haddock-entry-regex)
(when (w3m-haddock-valid-heading)
(w3m-haddock-format-heading))
(w3m-haddock-next-heading))))
(rename-buffer (concat "*haddock: " (w3m-buffer-title (current-buffer)) "*")))
(when (w3m-haddock-source-p)
(font-lock-mode -1)
(let ((n (line-number-at-pos)))
(save-excursion
(goto-char (point-min))
(forward-line 1)
(let ((text (buffer-substring (point)
(point-max)))
(inhibit-read-only t))
(delete-region (point)
(point-max))
(insert
(haskell-fontify-as-mode text
'haskell-mode))))
(goto-char (point-min))
(forward-line (1- n)))))
(defun w3m-haddock-format-heading ()
"Format a haddock entry."
(let ((o (make-overlay (line-beginning-position)
(1- (save-excursion (w3m-haddock-header-end))))))
(overlay-put o 'face 'w3m-haddock-heading-face))
(let ((end (save-excursion
(w3m-haddock-next-heading)
(when (w3m-haddock-valid-heading)
(point)))))
(when end
(save-excursion
(w3m-haddock-header-end)
(indent-rigidly (point)
end
4)))))
(defun w3m-haddock-next-heading ()
"Go to the next heading, or end of the buffer."
(forward-line 1)
(or (search-forward-regexp w3m-haddock-entry-regex nil t 1)
(goto-char (point-max)))
(goto-char (line-beginning-position)))
(defun w3m-haddock-valid-heading ()
"Is this a valid heading?"
(not (get-text-property (point) 'face)))
(defun w3m-haddock-header-end ()
"Go to the end of the header."
(search-forward-regexp "\n[ \n]"))
(provide 'w3m-haddock)

Binary file not shown.