├── snippets ├── haskell-mode │ ├── .yas-make-groups │ ├── .yas-ignore-filenames-as-triggers │ ├── comment.block │ ├── lambda │ ├── let │ ├── get │ ├── constraint │ ├── if.inline │ ├── if.block │ ├── import │ ├── data.inline │ ├── newtype │ ├── case │ ├── instance │ ├── fn │ ├── data.record │ ├── lang-pragma │ ├── main │ ├── fn.clause │ ├── fn.guarded │ ├── import.qualified │ ├── module │ └── module.exports └── README.md ├── .gitignore ├── .github └── workflows │ └── ci.yml ├── purescript-vars.el ├── examples ├── fontlock.hs ├── init.el └── indent.hs ├── purescript-yas.el ├── purescript-presentation-mode.el ├── purescript-collapse.el ├── README.md ├── Makefile ├── tests ├── purescript-sort-imports-tests.el ├── purescript-indentation-tests.el └── purescript-font-lock-tests.el ├── purescript-navigate-imports.el ├── purescript-move-nested.el ├── purescript-sort-imports.el ├── purescript-simple-indent.el ├── purescript-unicode-input-method.el ├── purescript-align-imports.el ├── purescript-font-lock.el ├── purescript-mode.el ├── purescript-mode.texi └── purescript-decl-scan.el /snippets/haskell-mode/.yas-make-groups: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /snippets/haskell-mode/.yas-ignore-filenames-as-triggers: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.elc 2 | *~ 3 | purescript-mode-autoloads.el 4 | purescript-mode.info 5 | purescript-mode.tmp.texi 6 | dir 7 | -------------------------------------------------------------------------------- /snippets/haskell-mode/comment.block: -------------------------------------------------------------------------------- 1 | # -*- mode: snippet -*- 2 | # key: {- 3 | # name: block comment 4 | # contributor: Luke Hoersten 5 | # -- 6 | {- $0 -} -------------------------------------------------------------------------------- /snippets/haskell-mode/lambda: -------------------------------------------------------------------------------- 1 | # -*- mode: snippet -*- 2 | # key: \ 3 | # name: lambda 4 | # contributor: Luke Hoersten 5 | # -- 6 | \\${1:x} -> ${2:undefined}$0 -------------------------------------------------------------------------------- /snippets/haskell-mode/let: -------------------------------------------------------------------------------- 1 | # -*- mode: snippet -*- 2 | # key: let 3 | # name: let 4 | # contributor: Luke Hoersten 5 | # -- 6 | let ${1:x} = ${2:undefined}$0 -------------------------------------------------------------------------------- /snippets/haskell-mode/get: -------------------------------------------------------------------------------- 1 | # -*- mode: snippet -*- 2 | # key: <- 3 | # name: monadic get 4 | # contributor: Luke Hoersten 5 | # -- 6 | ${1:x} <- ${2:undefined}$0 -------------------------------------------------------------------------------- /snippets/haskell-mode/constraint: -------------------------------------------------------------------------------- 1 | # -*- mode: snippet -*- 2 | # key: => 3 | # name: Type constraint 4 | # contributor: Luke Hoersten 5 | # -- 6 | (${1:Class} ${2:m}) => $0 -------------------------------------------------------------------------------- /snippets/haskell-mode/if.inline: -------------------------------------------------------------------------------- 1 | # -*- mode: snippet -*- 2 | # key: if 3 | # name: inline if 4 | # contributor: Luke Hoersten 5 | # -- 6 | if ${1:condition} then ${2:undefined} else ${3:undefined}$0 -------------------------------------------------------------------------------- /snippets/haskell-mode/if.block: -------------------------------------------------------------------------------- 1 | # -*- mode: snippet -*- 2 | # key: if 3 | # name: block if 4 | # contributor: Luke Hoersten 5 | # -- 6 | if ${1:condition} 7 | then ${2:undefined} 8 | else ${3:undefined}$0 -------------------------------------------------------------------------------- /snippets/haskell-mode/import: -------------------------------------------------------------------------------- 1 | # -*- mode: snippet -*- 2 | # key: imp 3 | # name: simple import 4 | # condition: (= (length "imp") (current-column)) 5 | # contributor: Luke Hoersten 6 | # -- 7 | import ${1:Module} ${2:(${3:f})} -------------------------------------------------------------------------------- /snippets/haskell-mode/data.inline: -------------------------------------------------------------------------------- 1 | # -*- mode: snippet -*- 2 | # key: data 3 | # name: inline data 4 | # condition: (= (length "data") (current-column)) 5 | # contributor: Luke Hoersten 6 | # -- 7 | data ${1:Type} = ${2:Data}$0 ${3:deriving (${4:Show, Eq})} -------------------------------------------------------------------------------- /snippets/haskell-mode/newtype: -------------------------------------------------------------------------------- 1 | # -*- mode: snippet -*- 2 | # key: new 3 | # name: newtype 4 | # condition: (= (length "new") (current-column)) 5 | # contributor: Luke Hoersten 6 | # -- 7 | newtype ${1:Type} = $1 { un$1 :: ${2:a} } ${3:deriving (${4:Show, Eq})} -------------------------------------------------------------------------------- /snippets/haskell-mode/case: -------------------------------------------------------------------------------- 1 | # -*- mode: snippet -*- 2 | # key: case 3 | # name: case 4 | # expand-env: ((yas-indent-line 'fixed)) 5 | # contributor: Luke Hoersten 6 | # -- 7 | case ${1:x} of 8 | ${2:Data} -> ${4:undefined} 9 | ${3:Data} -> ${5:undefined}$0 -------------------------------------------------------------------------------- /snippets/haskell-mode/instance: -------------------------------------------------------------------------------- 1 | # -*- mode: snippet -*- 2 | # key: inst 3 | # name: instance 4 | # condition: (= (length "inst") (current-column)) 5 | # contributor: Luke Hoersten 6 | # -- 7 | instance ${1:Class} ${2:Data} where 8 | ${3:f} = ${4:undefined}$0 -------------------------------------------------------------------------------- /snippets/haskell-mode/fn: -------------------------------------------------------------------------------- 1 | # -*- mode: snippet -*- 2 | # key: fn 3 | # name: simple function 4 | # condition: (= (length "fn") (current-column)) 5 | # expand-env: ((yas-indent-line 'fixed)) 6 | # contributor: Luke Hoersten 7 | # -- 8 | ${1:f} :: ${2:a} ${3:-> ${4:b}} 9 | $1 ${5:x} = ${6:undefined}$0 -------------------------------------------------------------------------------- /snippets/haskell-mode/data.record: -------------------------------------------------------------------------------- 1 | # -*- mode: snippet -*- 2 | # key: data 3 | # name: record data 4 | # condition: (= (length "data") (current-column)) 5 | # contributor: Luke Hoersten 6 | # -- 7 | data ${1:Type} = $1 8 | { ${2:field} :: ${3:Type} 9 | , ${4:field} :: ${5:Type}$0 10 | } ${6:deriving (${7:Show, Eq})} -------------------------------------------------------------------------------- /snippets/haskell-mode/lang-pragma: -------------------------------------------------------------------------------- 1 | # -*- mode: snippet -*- 2 | # key: lang 3 | # name: language pragma 4 | # condition: (= (length "lang") (current-column)) 5 | # contributor: Luke Hoersten , John Wiegley 6 | # -- 7 | {-# LANGUAGE `(progn (require 'purescript-yas) (purescript-yas-complete "Extension: " purescript-yas-ghc-language-pragmas))` #-} -------------------------------------------------------------------------------- /snippets/haskell-mode/main: -------------------------------------------------------------------------------- 1 | # -*- mode: snippet -*- 2 | # key: main 3 | # name: main module 4 | # condition: (= (length "main") (current-column)) 5 | # expand-env: ((yas-indent-line 'fixed)) 6 | # contributor: Luke Hoersten 7 | # -- 8 | module Main where 9 | 10 | main :: IO () 11 | main = do 12 | ${1:undefined}$0 13 | return () -------------------------------------------------------------------------------- /snippets/haskell-mode/fn.clause: -------------------------------------------------------------------------------- 1 | # -*- mode: snippet -*- 2 | # key: fn 3 | # name: clause function 4 | # condition: (= (length "fn") (current-column)) 5 | # expand-env: ((yas-indent-line 'fixed)) 6 | # contributor: Luke Hoersten 7 | # -- 8 | ${1:f} :: ${2:a} ${3:-> ${4:b}} 9 | $1 ${5:pattern} = ${7:undefined} 10 | $1 ${6:pattern} = ${8:undefined}$0 -------------------------------------------------------------------------------- /snippets/haskell-mode/fn.guarded: -------------------------------------------------------------------------------- 1 | # -*- mode: snippet -*- 2 | # key: fn 3 | # name: guarded function 4 | # condition: (= (length "fn") (current-column)) 5 | # expand-env: ((yas-indent-line 'fixed)) 6 | # contributor: Luke Hoersten 7 | # -- 8 | ${1:f} :: ${2:a} ${3:-> ${4:b}} 9 | $1 ${5:x} 10 | | ${6:conditional} = ${8:undefined} 11 | | ${7:conditional} = ${9:undefined}$0 -------------------------------------------------------------------------------- /snippets/haskell-mode/import.qualified: -------------------------------------------------------------------------------- 1 | # -*- mode: snippet -*- 2 | # key: imp 3 | # name: qualified import 4 | # condition: (= (length "imp") (current-column)) 5 | # contributor: Luke Hoersten 6 | # -- 7 | import qualified ${1:Module} as ${2:${1:$(let ((name (car (last (split-string yas-text "\\\."))))) 8 | (if (= 0 (length name)) "" 9 | (subseq name 0 1)))}}$0 -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | paths-ignore: 7 | - '**.md' 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | emacs_version: 15 | - 25.1 16 | - 26.1 17 | - 27.1 18 | - 28.1 19 | - snapshot 20 | steps: 21 | - uses: purcell/setup-emacs@master 22 | with: 23 | version: ${{ matrix.emacs_version }} 24 | 25 | - uses: actions/checkout@v2 26 | - name: Run tests 27 | run: make test 28 | -------------------------------------------------------------------------------- /snippets/haskell-mode/module: -------------------------------------------------------------------------------- 1 | # -*- mode: snippet -*- 2 | # key: mod 3 | # name: simple module 4 | # condition: (= (length "mod") (current-column)) 5 | # expand-env: ((yas-indent-line 'fixed)) 6 | # contributor: Luke Hoersten 7 | # -- 8 | module ${1:`(if (not buffer-file-name) "Module" 9 | (let ((name (file-name-sans-extension (buffer-file-name)))) 10 | (if (search "src/" name) 11 | (replace-regexp-in-string "/" "." (car (last (split-string name "src/")))) 12 | (file-name-nondirectory name))))`} where 13 | 14 | $0 -------------------------------------------------------------------------------- /snippets/haskell-mode/module.exports: -------------------------------------------------------------------------------- 1 | # -*- mode: snippet -*- 2 | # key: mod 3 | # name: exports module 4 | # condition: (= (length "mod") (current-column)) 5 | # expand-env: ((yas-indent-line 'fixed)) 6 | # contributor: Luke Hoersten 7 | # -- 8 | module ${1:`(if (not buffer-file-name) "Module" 9 | (let ((name (file-name-sans-extension (buffer-file-name)))) 10 | (if (search "src/" name) 11 | (replace-regexp-in-string "/" "." (car (last (split-string name "src/")))) 12 | (file-name-nondirectory name))))`} 13 | ( ${3:export} 14 | ${4:, ${5:export}} 15 | ) where 16 | 17 | $0 -------------------------------------------------------------------------------- /purescript-vars.el: -------------------------------------------------------------------------------- 1 | ;;; purescript-vars.el --- Variable definitions for PureScript Mode -*- lexical-binding: t -*- 2 | 3 | ;; Author: 1997-1998 Graeme E Moss 4 | ;; 1997-1998 Tommy Thorn 5 | ;; 2003 Dave Love 6 | ;; 2025 Konstantin Kharlamov 7 | 8 | ;; This file is not part of GNU Emacs. 9 | 10 | ;; This file is free software; you can redistribute it and/or modify 11 | ;; it under the terms of the GNU General Public License as published by 12 | ;; the Free Software Foundation; either version 3, or (at your option) 13 | ;; any later version. 14 | 15 | ;; This file is distributed in the hope that it will be useful, 16 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | ;; GNU General Public License for more details. 19 | 20 | ;; You should have received a copy of the GNU General Public License 21 | ;; along with this program. If not, see . 22 | 23 | (defvar-local purescript-literate nil 24 | "If not nil, the current buffer contains a literate PureScript script. 25 | Possible values are: `bird' and `tex', for Bird-style and LaTeX-style 26 | literate scripts respectively. Set by `purescript-mode' and 27 | `literate-purescript-mode'. For an ambiguous literate buffer -- i.e. does 28 | not contain either \"\\begin{code}\" or \"\\end{code}\" on a line on 29 | its own, nor does it contain \">\" at the start of a line -- the value 30 | of `purescript-literate-default' is used.") 31 | (put 'purescript-literate 'safe-local-variable 'symbolp) 32 | 33 | (provide 'purescript-vars) 34 | -------------------------------------------------------------------------------- /examples/fontlock.hs: -------------------------------------------------------------------------------- 1 | -- Comments are coloured brightly and stand out clearly. 2 | 3 | import qualified Foo as F hiding (toto) 4 | import qualified Foo hiding (toto) 5 | import qualified Foo as F (toto) 6 | import Foo as F hiding (toto) 7 | import Foo hiding (toto) 8 | import Foo as F (toto) 9 | 10 | hiding = 1 11 | qualified = 3 12 | as = 2 13 | 14 | repeat :: a -> [a] 15 | repeat xs = xs where xs = x:xs -- Keywords are also bright. 16 | 17 | head :: [a] -> a 18 | head (x:_) = x 19 | head [] = error "PreludeList.head: empty list" -- Strings are coloured softly. 20 | 21 | data Maybe a = Nothing | Just a -- Type constructors, data 22 | deriving (Eq, Ord, Read, Show) -- constructors, class names 23 | -- and module names are coloured 24 | -- closer to ordinary code. 25 | 26 | recognize +++ infix :: Operator Declarations 27 | as `well` as = This Form 28 | (+) and this one = as well 29 | 30 | instance Show Toto where 31 | fun1 arg1 = foo -- FIXME: `fun1' should be highlighted. 32 | 33 | constStr = "hello \ 34 | \asdgfasgf\ 35 | \asf" 36 | 37 | {- 38 | map :: (a -> b) -> [a] -> [b] -- Commenting out large sections of 39 | map f [] = [] -- code can be misleading. Coloured 40 | map f (x:xs) = f x : map f xs -- comments reveal unused definitions. 41 | -} 42 | 43 | -- Note: the least significant bit is the first element of the list 44 | bdigits :: Int -> [Int] 45 | bdigits 0 = [0] 46 | bdigits 1 = [1] 47 | bdigits n | n>1 = n `mod` 2 : 48 | 49 | -- arch-tag: a0d08cc2-4a81-4139-93bc-b3c6be0b5fb2 50 | -------------------------------------------------------------------------------- /purescript-yas.el: -------------------------------------------------------------------------------- 1 | ;;; purescript-yas.el --- Customization support for Luke Hoersten's yasnippets -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2013 John Wiegley, Luke Hoersten 4 | 5 | ;; Author: John Wiegley 6 | ;; Luke Hoersten 7 | ;; Keywords: faces files PureScript 8 | 9 | ;; This file is not part of GNU Emacs. 10 | 11 | ;; This file is free software; you can redistribute it and/or modify 12 | ;; it under the terms of the GNU General Public License as published by 13 | ;; the Free Software Foundation; either version 3, or (at your option) 14 | ;; any later version. 15 | 16 | ;; This file is distributed in the hope that it will be useful, 17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | ;; GNU General Public License for more details. 20 | 21 | ;; You should have received a copy of the GNU General Public License 22 | ;; along with this program. If not, see . 23 | 24 | ;;; Commentary: 25 | 26 | ;; Provides customization variables for Luke Hoersten's yasnippet collection 27 | ;; to depend on. 28 | 29 | ;;; Code: 30 | 31 | (defgroup purescript-yas nil 32 | "Customizations for Luke Hoersten's yasnippet collection for purescript-mode." 33 | :group 'purescript 34 | :prefix "purescript-yas-") 35 | 36 | (defcustom purescript-yas-completing-function 'completing-read 37 | "Function to use for completing among alternatives." 38 | :group 'purescript-yas 39 | :type 'function) 40 | 41 | ;;;###autoload 42 | (defun purescript-yas-complete (&rest args) 43 | (apply purescript-yas-completing-function args)) 44 | 45 | ;; Provide ourselves: 46 | 47 | (provide 'purescript-yas) 48 | 49 | ;;; purescript-yas.el ends here 50 | -------------------------------------------------------------------------------- /purescript-presentation-mode.el: -------------------------------------------------------------------------------- 1 | ;;; purescript-presentation-mode.el --- Presenting PureScript things -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2013 Chris Done 4 | 5 | ;; Author: Chris Done 6 | 7 | ;; This file is not part of GNU Emacs. 8 | 9 | ;; This file is free software; you can redistribute it and/or modify 10 | ;; it under the terms of the GNU General Public License as published by 11 | ;; the Free Software Foundation; either version 3, or (at your option) 12 | ;; any later version. 13 | 14 | ;; This file is distributed in the hope that it will be useful, 15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | ;; GNU General Public License for more details. 18 | 19 | ;; You should have received a copy of the GNU General Public License 20 | ;; along with GNU Emacs; see the file COPYING. If not, write to 21 | ;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 22 | ;; Boston, MA 02110-1301, USA. 23 | 24 | ;;; Commentary: 25 | 26 | ;;; Code: 27 | 28 | (require 'purescript-mode) 29 | 30 | (define-derived-mode purescript-presentation-mode 31 | purescript-mode "Presentation" 32 | "Major mode for viewing PureScript snippets. 33 | \\{hypertext-mode-map}" 34 | (setq case-fold-search nil)) 35 | 36 | (define-key purescript-presentation-mode-map (kbd "q") 'quit-window) 37 | 38 | (defun purescript-present (name _ code) 39 | "Present CODE in a popup buffer suffixed with NAME and set 40 | SESSION as the current purescript-session." 41 | (let* ((name (format "*PureScript Presentation%s*" name)) 42 | (buffer (get-buffer-create name))) 43 | (with-current-buffer buffer 44 | (purescript-presentation-mode) 45 | (if (boundp 'shm-display-quarantine) 46 | (set (make-local-variable 'shm-display-quarantine) nil)) 47 | (let ((buffer-read-only nil)) 48 | (erase-buffer) 49 | (insert (propertize "-- Hit `q' to close this window.\n\n" 50 | 'face 51 | 'font-lock-comment-face)) 52 | (let ((point (point))) 53 | (insert code "\n\n") 54 | (font-lock-fontify-region point (point)) 55 | (goto-char point)))) 56 | (if (and (boundp 'purescript-presentation-mode) 57 | purescript-presentation-mode) 58 | (switch-to-buffer buffer) 59 | (pop-to-buffer buffer)))) 60 | 61 | (provide 'purescript-presentation-mode) 62 | 63 | ;;; purescript-presentation-mode.el ends here 64 | -------------------------------------------------------------------------------- /purescript-collapse.el: -------------------------------------------------------------------------------- 1 | ;;; purescript-collapse.el --- Collapse expressions -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (c) 2014 Chris Done. All rights reserved. 4 | 5 | ;; This file is free software; you can redistribute it and/or modify 6 | ;; it under the terms of the GNU General Public License as published by 7 | ;; the Free Software Foundation; either version 3, or (at your option) 8 | ;; any later version. 9 | 10 | ;; This file is distributed in the hope that it will be useful, 11 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | ;; GNU General Public License for more details. 14 | 15 | ;; You should have received a copy of the GNU General Public License 16 | ;; along with this program. If not, see . 17 | 18 | ;;; Code: 19 | 20 | (define-button-type 'purescript-collapse-toggle-button 21 | 'action 'purescript-collapse-toggle-button-callback 22 | 'follow-link t 23 | 'help-echo "Click to expand…") 24 | 25 | (defun purescript-collapse (beg end) 26 | "Collapse." 27 | (interactive "r") 28 | (goto-char end) 29 | (let ((break nil)) 30 | (while (and (not break) 31 | (search-backward-regexp "[[({]" beg t 1)) 32 | (unless (eq (get-text-property (point) 'face) 'font-lock-string-face) 33 | (let ((orig (point))) 34 | (purescript-collapse-sexp) 35 | (goto-char orig) 36 | (forward-char -1) 37 | (when (= (point) orig) 38 | (setq break t))))))) 39 | 40 | (defun purescript-collapse-sexp () 41 | "Collapse the sexp starting at point." 42 | (let ((beg (point))) 43 | (forward-sexp) 44 | (let ((end (point))) 45 | (let ((o (make-overlay beg end))) 46 | (overlay-put o 'invisible t) 47 | (let ((start (point))) 48 | (insert "…") 49 | (let ((button (make-text-button start (point) 50 | :type 'purescript-collapse-toggle-button))) 51 | (button-put button 'overlay o) 52 | (button-put button 'hide-on-click t))))))) 53 | 54 | (defun purescript-collapse-toggle-button-callback (btn) 55 | "The callback to toggle the overlay visibility." 56 | (let ((overlay (button-get btn 'overlay))) 57 | (when overlay 58 | (overlay-put overlay 59 | 'invisible 60 | (not (overlay-get overlay 61 | 'invisible))))) 62 | (button-put btn 'invisible t) 63 | (delete-region (button-start btn) (button-end btn))) 64 | 65 | (provide 'purescript-collapse) 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![MELPA](https://melpa.org/packages/purescript-mode-badge.svg)](https://melpa.org/#/purescript-mode) 2 | [![Build Status](https://github.com/purescript-emacs/purescript-mode/workflows/CI/badge.svg)](https://github.com/purescript-emacs/purescript-mode/actions) 3 | 4 | This Emacs package provides indentation, syntax highlighting, and other facilities for PureScript language. 5 | 6 | Installation 7 | ------------ 8 | 9 | GNU Emacs version 25.1 or later is officially supported. It may work 10 | with other Emacsen, but we don't have the resources to support other 11 | versions. 12 | 13 | ### Installation using package.el 14 | 15 | Users of [MELPA](https://melpa.org) can install `purescript-mode` 16 | using `M-x package-install`. This is the most straightforward 17 | and recommended installation method. 18 | 19 | ### Installation from Git 20 | 21 | - `git clone https://github.com/purescript-emacs/purescript-mode.git` into a 22 | suitable directory, e.g. `~/lib/emacs/purescript-mode/` where `~` 23 | stands for your home directory. 24 | 25 | - Assuming you have unpacked the various purescript-mode modules 26 | (`purescript-mode.el` and the rest) in the directory 27 | `~/lib/emacs/purescript-mode/`, you need generate the autoloads file 28 | (`purescript-mode-autoloads.el`) by either 29 | 30 | - Invoking `make purescript-mode-autoloads.el`, or `make all` (use 31 | this to perform byte-compilation and Info manual generation) 32 | 33 | - From inside Emacs, `M-x update-directory-autoloads` and answering the question for 34 | the folder with `~/lib/emacs/purescript-mode/` and the question for the output-file with 35 | `~/lib/emacs/purescript-mode/purescript-mode-autoloads.el` 36 | 37 | and then adding the following command to your `.emacs`: 38 | 39 | ```el 40 | (add-to-list 'load-path "~/lib/emacs/purescript-mode/") 41 | (require 'purescript-mode-autoloads) 42 | (add-to-list 'Info-default-directory-list "~/lib/emacs/purescript-mode/") 43 | ``` 44 | 45 | - After updating your purescript-mode working directory, you need to 46 | re-run `make all` or `M-x update-directory-autoloads`. 47 | 48 | Basic Configuration 49 | ------------------- 50 | 51 | PureScript mode provides multiple indentation engines, and leaves the choice up to the user. To have indentation an according indentation mode needs to be enabled. Otherwise, attempting to indent will print an error describing this. 52 | 53 | Minimal configuration may look something like: 54 | 55 | ```lisp 56 | (use-package purescript-mode 57 | :defer t 58 | :config 59 | (defun myhook-purescript-mode () 60 | (turn-on-purescript-indentation) 61 | (add-hook 'before-save-hook #'purescript-sort-imports nil t)) 62 | (add-hook 'purescript-mode-hook #'myhook-purescript-mode)) 63 | ``` 64 | -------------------------------------------------------------------------------- /snippets/README.md: -------------------------------------------------------------------------------- 1 | # Shnippet 2 | 3 | 4 | **Shnippet** is a collection of 5 | [YASnippet][yas] 6 | [PureScript][purescript] snippets for Emacs. 7 | 8 | 9 | ## Installation 10 | 11 | Clone repository: 12 | 13 | $ cd ~/.emacs.d/snippets 14 | $ git clone https://github.com/LukeHoersten/shnippet 15 | OR 16 | $ hg clone https://bitbucket.org/LukeHoersten/shnippet 17 | 18 | Add the cloned repository to YASnippet's `yas-snippet-dirs`: 19 | 20 | (setq yas-snippet-dirs 21 | '("~/.emacs.d/snippets/shnippet" 22 | "/other/patpurs/" 23 | )) 24 | 25 | Snippets may have to be recompiled and reloaded in Emacs if YASnippet 26 | is already in use: 27 | 28 | M-x yas-recompile-all 29 | M-x yas-reload-all 30 | 31 | 32 | PureScript snippts should now be available to use! In a `purescript-mode` 33 | buffer, type `fn`. A prompt should appear asking which `fn` 34 | snippet to expand. 35 | 36 | I **highly** recommend using YASnippet with [ido-mode]. Configure 37 | Emacs: 38 | 39 | (setq-default yas-prompt-functions '(yas-ido-prompt yas-dropdown-prompt)) 40 | 41 | This is important so that alternatives (like `import` vs. `import 42 | qualified`) can quickly be selected with a single key stroke. 43 | 44 | 45 | ## Available Expansion Keys 46 | 47 | * `new` - newtype 48 | * `mod` - module [simple, exports] 49 | * `main ` - main module and funtion 50 | * `let` - let bindings 51 | * `lang` - language extension pragmas 52 | * `\` - lambda function 53 | * `inst` - instance declairation 54 | * `imp` - import modules [simple, qualified] 55 | * `if` - if conditional [inline, block] 56 | * `<-` - monadic get 57 | * `fn` - top level function [simple, guarded, clauses] 58 | * `data` - data type definition [inline, record] 59 | * `=>` - type constraint 60 | * `{-` - block comment 61 | * `case` - case statement 62 | 63 | 64 | ## Design Ideals 65 | 66 | * Keep snippet keys (the prefix used to auto-complete) to four 67 | characters or less while still being as easy to guess as possible. 68 | 69 | * Have as few keys as possible. The more keys there are to remember, 70 | the harder snippets are to use and learn. 71 | 72 | * Leverage [ido-mode][] when reasonable. For instance, to keep the 73 | number of snippet keys to a minimum as well as auto complete things 74 | like [PureScript Langauge Extension Pragmas][lang-pragma]. When 75 | multiple snippets share a key (ex: `fn`), the `ido-mode` prompts are 76 | unique to one character (ex: `guarded function` and `simple 77 | function` are `g` and `s` respectively). 78 | 79 | 80 | ## Authors 81 | 82 | This code is written and maintained by Luke Hoersten, 83 | . 84 | 85 | 86 | [yas]: https://github.com/capitaomorte/yasnippet 87 | [ido-mode]: http://www.emacswiki.org/emacs/InteractivelyDoThings 88 | [lang-pragma]: http://hackage.purescript.org/packages/archive/Cabal/1.16.0.3/doc/html/Language-PureScript-Extension.html#t:KnownExtension 89 | [purescript]: http://purescript.org/ 90 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION = $(shell git describe --tags --match 'v[0-9]*' --abbrev=0 | sed 's/^v//;s/\.0*/./g') 2 | GIT_VERSION = $(shell git describe --tags --match 'v[0-9]*' --long --dirty | sed 's/^v//') 3 | 4 | INSTALL_INFO = install-info 5 | EMACS = emacs 6 | EFLAGS = 7 | BATCH = $(EMACS) $(EFLAGS) --batch -Q -L . 8 | SUBST_ATAT = sed -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g;s/@GIT_VERSION@/$(GIT_VERSION)/g;s/@@VERSION@@/$(VERSION)/g;s/@VERSION@/$(VERSION)/g' 9 | 10 | ELFILES = \ 11 | purescript-align-imports.el \ 12 | purescript-collapse.el \ 13 | purescript-font-lock.el \ 14 | purescript-indent.el \ 15 | purescript-indentation.el \ 16 | purescript-mode.el \ 17 | purescript-move-nested.el \ 18 | purescript-navigate-imports.el \ 19 | purescript-presentation-mode.el \ 20 | purescript-simple-indent.el \ 21 | purescript-sort-imports.el \ 22 | purescript-unicode-input-method.el \ 23 | purescript-decl-scan.el \ 24 | purescript-yas.el \ 25 | purescript-vars.el \ 26 | tests/purescript-sort-imports-tests.el \ 27 | tests/purescript-indentation-tests.el \ 28 | tests/purescript-font-lock-tests.el \ 29 | 30 | ELCFILES = $(ELFILES:.el=.elc) 31 | AUTOLOADS = purescript-mode-autoloads.el 32 | 33 | PKG_DIST_FILES = $(ELFILES) purescript-mode.info dir 34 | PKG_TAR = purescript-mode-$(VERSION).tar 35 | 36 | %.elc: %.el 37 | @$(BATCH) \ 38 | --eval "(setq byte-compile-error-on-warn t)" -f batch-byte-compile $(ELFILES) 39 | 40 | .PHONY: all compile info clean test elpa package 41 | 42 | all: compile $(AUTOLOADS) info 43 | 44 | compile: $(ELCFILES) 45 | 46 | test: compile 47 | @$(BATCH) -l tests/purescript-sort-imports-tests.elc \ 48 | -l tests/purescript-indentation-tests.elc \ 49 | -l tests/purescript-font-lock-tests.elc \ 50 | -f ert-run-tests-batch-and-exit 51 | @echo "tests passed!" 52 | 53 | clean: 54 | $(RM) $(ELCFILES) $(AUTOLOADS) $(AUTOLOADS:.el=.elc) $(PKG_TAR) purescript-mode.tmp.texi purescript-mode.info dir 55 | 56 | info: purescript-mode.info dir 57 | 58 | dir: purescript-mode.info 59 | $(INSTALL_INFO) --dir=$@ $< 60 | 61 | purescript-mode.tmp.texi: purescript-mode.texi 62 | $(SUBST_ATAT) < purescript-mode.texi > purescript-mode.tmp.texi 63 | 64 | purescript-mode.info: purescript-mode.tmp.texi 65 | $(MAKEINFO) $(MAKEINFO_FLAGS) -o $@ $< 66 | 67 | purescript-mode.html: purescript-mode.tmp.texi 68 | $(MAKEINFO) $(MAKEINFO_FLAGS) --html --no-split -o $@ $< 69 | 70 | # Generate ELPA-compatible package 71 | package: $(PKG_TAR) 72 | elpa: $(PKG_TAR) 73 | 74 | $(PKG_TAR): $(PKG_DIST_FILES) purescript-mode-pkg.el.in 75 | rm -rf purescript-mode-$(VERSION) 76 | mkdir purescript-mode-$(VERSION) 77 | cp $(PKG_DIST_FILES) purescript-mode-$(VERSION)/ 78 | $(SUBST_ATAT) < purescript-mode-pkg.el.in > purescript-mode-$(VERSION)/purescript-mode-pkg.el 79 | $(SUBST_ATAT) < purescript-mode.el > purescript-mode-$(VERSION)/purescript-mode.el 80 | (sed -n -e '/^;;; Commentary/,/^;;;/p' | egrep '^;;( |$$)' | cut -c4-) < purescript-mode.el > purescript-mode-$(VERSION)/README 81 | tar cvf $@ purescript-mode-$(VERSION) 82 | rm -rf purescript-mode-$(VERSION) 83 | @echo 84 | @echo "Created ELPA compatible distribution package '$@' from $(GIT_VERSION)" 85 | 86 | $(AUTOLOADS): $(ELFILES) purescript-mode.elc 87 | $(BATCH) \ 88 | --eval '(setq make-backup-files nil)' \ 89 | --eval '(setq generated-autoload-file "$(CURDIR)/$@")' \ 90 | --eval "(require 'autoload)" \ 91 | -f batch-update-autoloads "." 92 | 93 | # HACK: embed version number into .elc file 94 | purescript-mode.elc: purescript-mode.el 95 | $(SUBST_ATAT) < purescript-mode.el > purescript-mode.tmp.el 96 | @$(BATCH) -f batch-byte-compile purescript-mode.tmp.el 97 | mv purescript-mode.tmp.elc purescript-mode.elc 98 | $(RM) purescript-mode.tmp.el 99 | -------------------------------------------------------------------------------- /tests/purescript-sort-imports-tests.el: -------------------------------------------------------------------------------- 1 | ;;; purescript-sort-imports-tests.el --- Unit tests for purescript-sort-imports -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (c) 2014 Chris Done. All rights reserved. 4 | 5 | ;; This file is free software; you can redistribute it and/or modify 6 | ;; it under the terms of the GNU General Public License as published by 7 | ;; the Free Software Foundation; either version 3, or (at your option) 8 | ;; any later version. 9 | 10 | ;; This file is distributed in the hope that it will be useful, 11 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | ;; GNU General Public License for more details. 14 | 15 | ;; You should have received a copy of the GNU General Public License 16 | ;; along with this program. If not, see . 17 | 18 | ;;; Code: 19 | 20 | (require 'ert) 21 | (require 'purescript-sort-imports) 22 | 23 | (ert-deftest empty-buffer () 24 | (with-temp-buffer 25 | (purescript-sort-imports) 26 | t)) 27 | 28 | (ert-deftest single-line () 29 | (with-temp-buffer 30 | (insert "import A\n") 31 | (goto-char (point-min)) 32 | (purescript-sort-imports) 33 | (should (string= (buffer-string) 34 | "import A\n")))) 35 | 36 | (ert-deftest two-idem () 37 | (with-temp-buffer 38 | (insert "import A 39 | import B 40 | ") 41 | (goto-char (point-min)) 42 | (purescript-sort-imports) 43 | (should (string= (buffer-string) 44 | "import A 45 | import B 46 | "))) 47 | (with-temp-buffer 48 | (insert "import A (A, B, C) 49 | import B 50 | ") 51 | (goto-char (point-min)) 52 | (purescript-sort-imports) 53 | (should (string= (buffer-string) 54 | "import A (A, B, C) 55 | import B 56 | "))) 57 | (with-temp-buffer 58 | (insert "import A (mtl) 59 | import B 60 | ") 61 | (goto-char (point-min)) 62 | (purescript-sort-imports) 63 | (should (string= (buffer-string) 64 | "import A (mtl) 65 | import B 66 | ")))) 67 | 68 | (ert-deftest two-rev () 69 | (with-temp-buffer 70 | (insert "import B 71 | import A 72 | ") 73 | (goto-char (point-min)) 74 | (purescript-sort-imports) 75 | (should (string= (buffer-string) 76 | "import A 77 | import B 78 | ")))) 79 | 80 | (ert-deftest file-structure () 81 | (with-temp-buffer 82 | (insert "module A where 83 | import B 84 | import A 85 | ") 86 | ;; test at line 2 87 | (goto-char (point-min)) 88 | (forward-line 1) 89 | (purescript-sort-imports) 90 | (should (string= (buffer-string) 91 | "module A where 92 | import A 93 | import B 94 | "))) 95 | (with-temp-buffer 96 | (insert "module C where 97 | 98 | import B 99 | import A 100 | ") 101 | ;; test at line 3 102 | (goto-char (point-min)) 103 | (forward-line 2) 104 | (purescript-sort-imports) 105 | (should (string= (buffer-string) 106 | "module C where 107 | 108 | import A 109 | import B 110 | ")))) 111 | 112 | (ert-deftest bos-270 () 113 | (with-temp-buffer 114 | (insert "import Data.Aeson.Encode (encode) 115 | import Data.Aeson.Types 116 | import Data.Aeson.Parser.Internal (decodeWith, decodeStrictWith, 117 | eitherDecodeWith, eitherDecodeStrictWith, 118 | jsonEOF, json, jsonEOF', json') 119 | import Data.ByteString as B 120 | import Data.ByteString.Lazy as L 121 | ") 122 | (goto-char (point-min)) 123 | (purescript-sort-imports) 124 | (should (string= (buffer-string) 125 | "import Data.Aeson.Encode (encode) 126 | import Data.Aeson.Parser.Internal (decodeWith, decodeStrictWith, 127 | eitherDecodeWith, eitherDecodeStrictWith, 128 | jsonEOF, json, jsonEOF', json') 129 | import Data.Aeson.Types 130 | import Data.ByteString as B 131 | import Data.ByteString.Lazy as L 132 | ")))) 133 | 134 | (provide 'purescript-sort-imports-tests) 135 | -------------------------------------------------------------------------------- /examples/init.el: -------------------------------------------------------------------------------- 1 | ;; Sample file for the new session/process stuff 2 | ;; Based on my own configuration. Well, it IS my configuration. 3 | ;; 4 | ;; NOTE: If you don't have cabal-dev, or you don't want to use it, you 5 | ;; should change purescript-process-type (see below) to 'ghci. 6 | ;; 7 | ;; To merely TRY this mode (and for debugging), do the below: 8 | ;; 9 | ;; cd into purescript-mode's directory, and run 10 | ;; $ emacs --load examples/init.el 11 | ;; 12 | ;; To get started, open a .purs file in one of your projects, and hit… 13 | ;; 14 | ;; 1. F5 to load the current file (and start a repl session), or 15 | ;; 2. C-` to just start a REPL associated with this project, or 16 | ;; 3. C-c C-c to build the cabal project (and start a repl session). 17 | 18 | ;; Add the current dir for loading purescript-site-file. 19 | (add-to-list 'load-path ".") 20 | ;; Always load via this. If you contribute you should run `make all` 21 | ;; to regenerate this. 22 | (load "purescript-mode-autoloads") 23 | 24 | ;; Customization 25 | (custom-set-variables 26 | ;; Use cabal-dev for the GHCi session. Ensures our dependencies are in scope. 27 | ;;'(purescript-process-type 'cabal-dev) 28 | 29 | ;; Use notify.el (if you have it installed) at the end of running 30 | ;; Cabal commands or generally things worth notifying. 31 | '(purescript-notify-p t) 32 | 33 | ;; To enable tags generation on save. 34 | '(purescript-tags-on-save t) 35 | 36 | ;; To enable stylish on save. 37 | '(purescript-stylish-on-save t)) 38 | 39 | (add-hook 'purescript-mode-hook 'purescript-hook) 40 | (add-hook 'purescript-cabal-mode-hook 'purescript-cabal-hook) 41 | 42 | ;; PureScript main editing mode key bindings. 43 | (defun purescript-hook () 44 | ;; Use simple indentation. 45 | (turn-on-purescript-simple-indent) 46 | (define-key purescript-mode-map (kbd "") 'purescript-simple-indent-newline-same-col) 47 | (define-key purescript-mode-map (kbd "C-") 'purescript-simple-indent-newline-indent) 48 | 49 | ;; Load the current file (and make a session if not already made). 50 | (define-key purescript-mode-map [?\C-c ?\C-l] 'purescript-process-load-file) 51 | (define-key purescript-mode-map [f5] 'purescript-process-load-file) 52 | 53 | ;; Switch to the REPL. 54 | (define-key purescript-mode-map [?\C-c ?\C-z] 'purescript-interactive-switch) 55 | ;; “Bring” the REPL, hiding all other windows apart from the source 56 | ;; and the REPL. 57 | (define-key purescript-mode-map (kbd "C-`") 'purescript-interactive-bring) 58 | 59 | ;; Build the Cabal project. 60 | (define-key purescript-mode-map (kbd "C-c C-c") 'purescript-process-cabal-build) 61 | ;; Interactively choose the Cabal command to run. 62 | (define-key purescript-mode-map (kbd "C-c c") 'purescript-process-cabal) 63 | 64 | ;; Get the type and info of the symbol at point, print it in the 65 | ;; message buffer. 66 | (define-key purescript-mode-map (kbd "C-c C-t") 'purescript-process-do-type) 67 | (define-key purescript-mode-map (kbd "C-c C-i") 'purescript-process-do-info) 68 | 69 | ;; Contextually do clever things on the space key, in particular: 70 | ;; 1. Complete imports, letting you choose the module name. 71 | ;; 2. Show the type of the symbol after the space. 72 | (define-key purescript-mode-map (kbd "SPC") 'purescript-mode-contextual-space) 73 | 74 | ;; Jump to the imports. Keep tapping to jump between import 75 | ;; groups. C-u f8 to jump back again. 76 | (define-key purescript-mode-map [f8] 'purescript-navigate-imports) 77 | 78 | ;; Jump to the definition of the current symbol. 79 | (define-key purescript-mode-map (kbd "M-.") 'purescript-mode-tag-find) 80 | 81 | ;; Indent the below lines on columns after the current column. 82 | (define-key purescript-mode-map (kbd "C-") 83 | (lambda () 84 | (interactive) 85 | (purescript-move-nested 1))) 86 | ;; Same as above but backwards. 87 | (define-key purescript-mode-map (kbd "C-") 88 | (lambda () 89 | (interactive) 90 | (purescript-move-nested -1)))) 91 | 92 | ;; Useful to have these keybindings for .cabal files, too. 93 | (defun purescript-cabal-hook () 94 | (define-key purescript-cabal-mode-map (kbd "C-c C-c") 'purescript-process-cabal-build) 95 | (define-key purescript-cabal-mode-map (kbd "C-c c") 'purescript-process-cabal) 96 | (define-key purescript-cabal-mode-map (kbd "C-`") 'purescript-interactive-bring) 97 | (define-key purescript-cabal-mode-map [?\C-c ?\C-z] 'purescript-interactive-switch)) 98 | -------------------------------------------------------------------------------- /purescript-navigate-imports.el: -------------------------------------------------------------------------------- 1 | ;;; purescript-navigate-imports.el --- A function for cycling through PureScript import lists -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2010 Chris Done 4 | 5 | ;; Author: Chris Done 6 | 7 | ;; This file is not part of GNU Emacs. 8 | 9 | ;; This program is free software: you can redistribute it and/or 10 | ;; modify it under the terms of the GNU General Public License as 11 | ;; published by the Free Software Foundation, either version 3 of 12 | ;; the License, or (at your option) any later version. 13 | 14 | ;; This program is distributed in the hope that it will be 15 | ;; useful, but WITHOUT ANY WARRANTY; without even the implied 16 | ;; warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 17 | ;; PURPOSE. See the GNU General Public License for more details. 18 | 19 | ;; You should have received a copy of the GNU General Public 20 | ;; License along with this program. If not, see 21 | ;; . 22 | 23 | ;;; Commentary: 24 | 25 | ;; The cycling step will stop once at the last import list so 26 | ;; that it is easy to add a new import list. 27 | 28 | ;; This module works completely independently of any libraries 29 | ;; (including purescript-mode). 30 | 31 | ;; Exports three interactive functions: 32 | ;; 1. purescript-navigate-imports 33 | ;; 2. purescript-navigate-imports-go 34 | ;; 3. purescript-navigate-imports-return 35 | 36 | ;; Example usage: 37 | 38 | ;; (require 'purescript-navigate-imports) 39 | ;; (define-key purescript-mode-map [f8] 'purescript-navigate-imports) 40 | 41 | ;;; Code: 42 | 43 | (defvar purescript-navigate-imports-start-point nil) 44 | 45 | ;;;###autoload 46 | (defun purescript-navigate-imports (&optional return) 47 | "Cycle the PureScript import lines or return to point (with prefix arg)." 48 | (interactive "P") 49 | (if return 50 | (purescript-navigate-imports-return) 51 | (purescript-navigate-imports-go))) 52 | 53 | ;;;###autoload 54 | (defun purescript-navigate-imports-go () 55 | "Go to the first line of a list of consequtive import lines. Cycles." 56 | (interactive) 57 | (unless (or (purescript-navigate-imports-line) 58 | (equal (line-beginning-position) (point-min)) 59 | (save-excursion (forward-line -1) 60 | (purescript-navigate-imports-line))) 61 | (setq purescript-navigate-imports-start-point (point))) 62 | (purescript-navigate-imports-go-internal)) 63 | 64 | ;;;###autoload 65 | (defun purescript-navigate-imports-return () 66 | "Return to the non-import point we were at before going to the module list. 67 | If we were originally at an import list, we can just cycle through easily." 68 | (interactive) 69 | (when purescript-navigate-imports-start-point 70 | (goto-char purescript-navigate-imports-start-point))) 71 | 72 | (defun purescript-navigate-imports-go-internal () 73 | "Go to the first line of a list of consequtive import lines. Cycle." 74 | (if (purescript-navigate-imports-line) 75 | (progn (purescript-navigate-imports-goto-end) 76 | (when (purescript-navigate-imports-find-forward-line) 77 | (purescript-navigate-imports-go-internal))) 78 | (let ((point (purescript-navigate-imports-find-forward-line))) 79 | (if point 80 | (goto-char point) 81 | (progn (goto-char (point-min)) 82 | (if (purescript-navigate-imports-find-forward-line) 83 | (purescript-navigate-imports-go-internal) 84 | (when (search-forward-regexp "^module" nil t 1) 85 | (search-forward "\n\n" nil t 1)))))))) 86 | 87 | (defun purescript-navigate-imports-goto-end () 88 | "Skip a bunch of consequtive import lines." 89 | (while (not (or (equal (point) 90 | (point-max)) 91 | (not (purescript-navigate-imports-line)))) 92 | (forward-line))) 93 | 94 | (defun purescript-navigate-imports-find-forward-line () 95 | "Return a point with at an import line, or nothing." 96 | (save-excursion 97 | (while (not (or (equal (point) (point-max)) 98 | (purescript-navigate-imports-after-imports-p) ;; This one just speeds it up. 99 | (purescript-navigate-imports-line))) 100 | (forward-line)) 101 | (if (purescript-navigate-imports-line) 102 | (point) 103 | nil))) 104 | 105 | (defun purescript-navigate-imports-line () 106 | "Try to match the current line as a regexp." 107 | (let ((line (buffer-substring-no-properties (line-beginning-position) 108 | (line-end-position)))) 109 | (if (string-match "^import " line) 110 | line 111 | nil))) 112 | 113 | (defun purescript-navigate-imports-after-imports-p () 114 | "Are we after the imports list? Just for a speed boost." 115 | (save-excursion 116 | (goto-char (line-beginning-position)) 117 | (not (not (search-forward-regexp "\\( = \\|\\\\| :: \\)" 118 | (line-end-position) t 1))))) 119 | 120 | (provide 'purescript-navigate-imports) 121 | 122 | ;;; purescript-navigate-imports.el ends here 123 | -------------------------------------------------------------------------------- /purescript-move-nested.el: -------------------------------------------------------------------------------- 1 | ;;; purescript-move-nested.el --- Change the column of text nested below a line -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2010 Chris Done 4 | 5 | ;; Author: Chris Done 6 | 7 | ;; This file is not part of GNU Emacs. 8 | 9 | ;; This program is free software: you can redistribute it and/or 10 | ;; modify it under the terms of the GNU General Public License as 11 | ;; published by the Free Software Foundation, either version 3 of 12 | ;; the License, or (at your option) any later version. 13 | 14 | ;; This program is distributed in the hope that it will be 15 | ;; useful, but WITHOUT ANY WARRANTY; without even the implied 16 | ;; warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 17 | ;; PURPOSE. See the GNU General Public License for more details. 18 | 19 | ;; You should have received a copy of the GNU General Public 20 | ;; License along with this program. If not, see 21 | ;; . 22 | 23 | ;;; Commentary: 24 | 25 | ;; This module is intended for PureScript mode users, but is 26 | ;; independent of PureScript mode. 27 | 28 | ;; Example usage: 29 | 30 | ;; (define-key purescript-mode-map (kbd "C-,") 'purescript-move-nested-left) 31 | ;; (define-key purescript-mode-map (kbd "C-.") 'purescript-move-nested-right) 32 | 33 | ;;; Code: 34 | 35 | ;;;###autoload 36 | (defun purescript-move-nested (cols) 37 | "Shift the nested off-side-rule block adjacent to point by COLS columns 38 | to the right. 39 | 40 | In Transient Mark mode, if the mark is active, operate on the contents 41 | of the region instead. 42 | " 43 | (save-excursion 44 | (if (and transient-mark-mode mark-active) 45 | (progn 46 | (indent-rigidly (region-beginning) (region-end) cols) 47 | (setq deactivate-mark nil)) 48 | (let ((region (purescript-move-nested-region))) 49 | (when region 50 | (indent-rigidly (car region) (cdr region) cols)))))) 51 | 52 | ;;;###autoload 53 | (defun purescript-move-nested-right (cols) 54 | "Increase indentation of the following off-side-rule block adjacent to point. 55 | 56 | Use a numeric prefix argument to indicate amount of indentation to apply. 57 | 58 | In Transient Mark mode, if the mark is active, operate on the contents 59 | of the region instead." 60 | (interactive "p") 61 | (purescript-move-nested cols) 62 | ) 63 | 64 | ;;;###autoload 65 | (defun purescript-move-nested-left (cols) 66 | "Decrease indentation of the following off-side-rule block adjacent to point. 67 | 68 | Use a numeric prefix argument to indicate amount of indentation to apply. 69 | 70 | In Transient Mark mode, if the mark is active, operate on the contents 71 | of the region instead." 72 | (interactive "p") 73 | (purescript-move-nested (- cols)) 74 | ) 75 | 76 | (defun purescript-move-nested-region () 77 | "Infer region off-side-rule block adjacent to point. 78 | Used by `purescript-move-nested'. 79 | " 80 | (save-excursion 81 | (let ((starting-level (current-column))) 82 | (forward-line) 83 | (let ((current-level (purescript-move-nested-indent-level))) 84 | (let ((start-point (line-beginning-position)) 85 | (start-end-point (line-end-position)) 86 | (end-point nil) 87 | (last-line 0)) 88 | (forward-line) 89 | (while (and (not (= (line-beginning-position) last-line)) 90 | (or (> (purescript-move-nested-indent-level) starting-level) 91 | (and (> current-level starting-level) 92 | (>= (purescript-move-nested-indent-level) current-level)))) 93 | (setq last-line (line-beginning-position)) 94 | (setq end-point (line-end-position)) 95 | (forward-line)) 96 | (cons start-point (or end-point 97 | start-end-point))))))) 98 | 99 | (defun purescript-move-nested-indent-level () 100 | (max 101 | 0 102 | (1- (length 103 | (buffer-substring-no-properties 104 | (line-beginning-position) 105 | (or (save-excursion (goto-char (line-beginning-position)) 106 | (search-forward-regexp "[^ ]" (line-end-position) t 1)) 107 | (line-beginning-position))))))) 108 | 109 | (defun purescript-kill-nested () 110 | "Kill the nested region after point." 111 | (interactive) 112 | (let ((start (point)) 113 | (reg (save-excursion 114 | (search-backward-regexp "^[ ]+" (line-beginning-position) t 1) 115 | (search-forward-regexp "[^ ]" (line-end-position) t 1) 116 | (purescript-move-nested-region)))) 117 | (kill-region start (cdr reg)))) 118 | 119 | (defun purescript-delete-nested () 120 | "Kill the nested region after point." 121 | (interactive) 122 | (let ((start (point)) 123 | (reg (save-excursion 124 | (search-backward-regexp "^[ ]+" (line-beginning-position) t 1) 125 | (search-forward-regexp "[^ ]" (line-end-position) t 1) 126 | (purescript-move-nested-region)))) 127 | (delete-region start (cdr reg)))) 128 | 129 | (provide 'purescript-move-nested) 130 | 131 | ;;; purescript-move-nested.el ends here 132 | -------------------------------------------------------------------------------- /purescript-sort-imports.el: -------------------------------------------------------------------------------- 1 | ;;; purescript-sort-imports.el --- Sort the list of PureScript imports at the point alphabetically -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2010 Chris Done 4 | 5 | ;; Author: Chris Done 6 | 7 | ;; This file is not part of GNU Emacs. 8 | 9 | ;; This program is free software: you can redistribute it and/or 10 | ;; modify it under the terms of the GNU General Public License as 11 | ;; published by the Free Software Foundation, either version 3 of 12 | ;; the License, or (at your option) any later version. 13 | 14 | ;; This program is distributed in the hope that it will be 15 | ;; useful, but WITHOUT ANY WARRANTY; without even the implied 16 | ;; warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 17 | ;; PURPOSE. See the GNU General Public License for more details. 18 | 19 | ;; You should have received a copy of the GNU General Public 20 | ;; License along with this program. If not, see 21 | ;; . 22 | 23 | ;;; Commentary: 24 | 25 | ;; If the region is active it sorts the imports within the 26 | ;; region. 27 | 28 | ;; This will align and sort the columns of the current import 29 | ;; list. It's more or less the coolest thing on the planet. 30 | 31 | ;;; Code: 32 | 33 | (defvar purescript-sort-imports-regexp 34 | (concat "^import[ ]+" 35 | "\\(\"[^\"]*\" \\)?" 36 | "[ ]*\\([A-Za-z0-9_.']*.*\\)")) 37 | 38 | (defun purescript-sort-imports () 39 | "Sort the import list at point. It sorts the current group 40 | i.e. an import list separated by blank lines on either side. 41 | 42 | If the region is active, it will restrict the imports to sort 43 | within that region." 44 | (interactive) 45 | (when (purescript-sort-imports-at-import) 46 | (let* ((points (purescript-sort-imports-decl-points)) 47 | (current-string (buffer-substring-no-properties (car points) 48 | (cdr points))) 49 | (current-offset (- (point) (car points)))) 50 | (if (region-active-p) 51 | (progn (goto-char (region-beginning)) 52 | (purescript-sort-imports-goto-import-start)) 53 | (purescript-sort-imports-goto-group-start)) 54 | (let ((start (point)) 55 | (imports (purescript-sort-imports-collect-imports))) 56 | (delete-region start (point)) 57 | (mapc (lambda (import) 58 | (insert import "\n")) 59 | (sort imports (lambda (a b) 60 | (string< (purescript-sort-imports-normalize a) 61 | (purescript-sort-imports-normalize b))))) 62 | (goto-char start) 63 | (when (search-forward current-string nil t 1) 64 | (forward-char (- (length current-string))) 65 | (forward-char current-offset)))))) 66 | 67 | (defun purescript-sort-imports-normalize (i) 68 | "Normalize an import, if possible, so that it can be sorted." 69 | (if (string-match purescript-sort-imports-regexp i) 70 | (match-string 2 i) 71 | i)) 72 | 73 | (defun purescript-sort-imports-collect-imports () 74 | (let ((imports (list))) 75 | (while (looking-at "import") 76 | (let* ((points (purescript-sort-imports-decl-points)) 77 | (string (buffer-substring-no-properties (car points) 78 | (cdr points)))) 79 | (goto-char (min (1+ (cdr points)) 80 | (point-max))) 81 | (setq imports (cons string imports)))) 82 | imports)) 83 | 84 | (defun purescript-sort-imports-goto-group-start () 85 | "Go to the start of the import group." 86 | (or (and (search-backward "\n\n" nil t 1) 87 | (goto-char (+ 2 (line-end-position)))) 88 | (when (search-backward-regexp "^module " nil t 1) 89 | (goto-char (1+ (line-end-position)))) 90 | (goto-char (point-min)))) 91 | 92 | (defun purescript-sort-imports-at-import () 93 | "Are we at an import?" 94 | (save-excursion 95 | (purescript-sort-imports-goto-import-start) 96 | (looking-at "import"))) 97 | 98 | (defun purescript-sort-imports-goto-import-start () 99 | "Go to the start of the import." 100 | (goto-char (car (purescript-sort-imports-decl-points)))) 101 | 102 | (defun purescript-sort-imports-decl-points () 103 | "Get the points of the declaration." 104 | (save-excursion 105 | (let ((start (or (progn (goto-char (line-end-position)) 106 | (search-backward-regexp "^[^ \n]" nil t 1) 107 | (unless (or (looking-at "^-}$") 108 | (looking-at "^{-$")) 109 | (point))) 110 | 0)) 111 | (end (progn (goto-char (1+ (point))) 112 | (or (when (search-forward-regexp "[\n]+[^ \n]" nil t 1) 113 | (forward-char -1) 114 | (search-backward-regexp "[^\n ]" nil t) 115 | (line-end-position)) 116 | (when (search-forward-regexp "\n" nil t 1) 117 | (1- (point))) 118 | (point-max))))) 119 | (cons start end)))) 120 | 121 | (provide 'purescript-sort-imports) 122 | 123 | ;;; purescript-sort-imports.el ends here 124 | -------------------------------------------------------------------------------- /examples/indent.hs: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------------- 2 | -- Comments with allcaps `FIXME' indicate places where the indentation -- 3 | -- fails to find the correct indentation, whereas comments with -- 4 | -- lowercase `fixme' indicate places where impossible indentations -- 5 | -- are uselessly proposed. -- 6 | ------------------------------------------------------------------------- 7 | 8 | -- | Fill-paragraph should avoid inserting an | on the following lines. 9 | 10 | -- | However, indented comments should still be indented. For great justice. 11 | 12 | -- * Foo bar bazFoo bar bazFoo bar bazFoo bar bazFoo bar bazFoo bar baz 13 | 14 | {- Here's 15 | a more complex comment. Of doom. There is, indeed, great doom here. #-} 16 | 17 | -- And a 18 | -- multi-line 19 | -- comment 20 | 21 | -- compute the list of binary digits corresponding to an integer 22 | -- Note: the least significant bit is the first element of the list 23 | bdigits :: Int -> [Int] -- | commented to oblivion and back and forth and so forth 24 | bdigits 0 = [0] 25 | bdigits 1 = [1] 26 | bdigits n | n>1 = n `mod` 2 : 27 | bdigits (n `div` 2) 28 | | otherwise = error "bdigits of a negative number" 29 | 30 | -- compute the value of an integer given its list of binary digits 31 | -- Note: the least significant bit is the first element of the list 32 | bvalue :: [Int]->Int 33 | bvalue [] = error "bvalue of []" 34 | bvalue s = bval 1 s 35 | where 36 | bval e [] = 0 37 | bval e [] = 0 -- fixme: can't align with `where'. 38 | bval e (b:bs) | b==0 || b=="dd of " = b*e + bval (2*e) bs 39 | | otherwise = error "ill digit" -- Spurious 3rd step. 40 | foo 41 | 42 | -- fixme: tab on the line above should insert `bvalue' at some point. 43 | 44 | {- text 45 | indentation 46 | inside comments 47 | -} 48 | toto a = ( hello 49 | , there -- indentation of leading , and ; 50 | -- indentation of this comment. 51 | , my friends ) 52 | 53 | lili x = do let ofs x = 1 54 | print x 55 | 56 | titi b = 57 | let -- fixme: can't indent at column 0 58 | x = let toto = 1 59 | tata = 2 -- fixme: can't indent lower than `toto'. 60 | in 61 | toto in 62 | do expr1 63 | {- text 64 | - indentation 65 | - inside comments 66 | -} 67 | let foo s = let fro = 1 68 | fri = 2 -- fixme: can't indent lower than `fro'. 69 | in 70 | hello 71 | foo2 = bar2 -- fixme: can't align with arg `s' in foo. 72 | foo1 = bar2 -- fixme: Can't be column 0. 73 | expr2 74 | 75 | tata c = 76 | let bar = case foo -- fixme: can't be col 0. 77 | of 1 -> blabla 78 | 2 -> blibli -- fixme: only one possible indentation here. 79 | bar = case foo of 80 | _ -> blabla 81 | bar' = case foo 82 | of _ -> blabla 83 | toto -> plulu 84 | 85 | turlu d = if test 86 | then 87 | ifturl 88 | else 89 | adfaf 90 | 91 | turlu d = if test then 92 | ifturl 93 | else 94 | sg 95 | 96 | turly fg = toto 97 | where 98 | hello = 2 99 | 100 | 101 | -- test from John Goerzen 102 | 103 | x myVariableThing = case myVariablething of 104 | Just z -> z 105 | Nothing -> 0 -- fixme: "spurious" additional indents. 106 | 107 | foo = let x = 1 in toto 108 | titi -- FIXME 109 | 110 | foo = let foo x y = toto 111 | where 112 | toto = 2 113 | 114 | instance Show Toto where 115 | foo x 4 = 50 116 | 117 | data Toto = Foo 118 | | Bar 119 | deriving (Show) -- FIXME 120 | 121 | foo = let toto x = do let bar = 2 122 | return 1 123 | in 3 124 | 125 | eval env (Llambda x e) = -- FIXME: sole indentation is self??? 126 | Vfun (\v -> eval (\y -> if (x == y) then v else env y) -- FIXME 127 | e) -- FIXME 128 | 129 | foo = case findprop attr props of 130 | Just x -> x 131 | 132 | data T = T { granularity :: (Int, Int, Int, Int) -- FIXME: self indentation? 133 | , items :: Map (Int, Int, Int, Int) [Item] } 134 | 135 | foo = case foo of 136 | [] -> 137 | case bar of 138 | [] -> 139 | return () 140 | (x:xs) -> -- FIXME 141 | 142 | bar = do toto 143 | if titi 144 | then tutu -- FIXME 145 | else tata -- FIXME 146 | 147 | insert :: Ord a => a -> b -> TreeMap a b -> TreeMap a b 148 | insert x v Empty = Node 0 x v Empty Empty 149 | insert x v (Node d x' v' t1 t2) 150 | | x == x' = Node d x v t1 t2 151 | | x < x' = Node ? x' v' (insert x v t1 Empty) t2 152 | | -- FIXME: wrong indent *if at EOB* 153 | 154 | 155 | tinsertb x v (Node x' v' d1 t1 d2 t2) 156 | | x == x' = (1 + max d1 d2, Node x v d1 t1 d2 t2) 157 | | x < x' = 158 | case () of 159 | _ | d1' <= d2 + 1 => (1 + max d1' d2, Node x' v' d1' t1' d2 t2) 160 | -- d1' == d2 + 2: Need to rotate to rebalance. FIXME CRASH 161 | else let (Node x'' v'' d1'' t1'' d2'' t2'') = t1' 162 | 163 | test = if True then 164 | toto 165 | else if False then 166 | tata -- FIXME 167 | else -- FIXME 168 | titi 169 | 170 | -- arch-tag: de0069e3-c0a0-495c-b441-d4ff6e0509b1 171 | -------------------------------------------------------------------------------- /tests/purescript-indentation-tests.el: -------------------------------------------------------------------------------- 1 | ;;; purescript-indentation-tests.el --- Unit tests for purescript indentation -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (c) 2025 Konstantin Kharlamov. All rights reserved. 4 | 5 | ;; This file is free software; you can redistribute it and/or modify 6 | ;; it under the terms of the GNU General Public License as published by 7 | ;; the Free Software Foundation; either version 3, or (at your option) 8 | ;; any later version. 9 | 10 | ;; This file is distributed in the hope that it will be useful, 11 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | ;; GNU General Public License for more details. 14 | 15 | ;; You should have received a copy of the GNU General Public License 16 | ;; along with this program. If not, see . 17 | 18 | ;;; Code: 19 | 20 | (require 'ert) 21 | (require 'purescript-mode) 22 | (require 'purescript-indentation) 23 | 24 | (defun purescript-test-indentation-expected-only (expected) 25 | (with-temp-buffer 26 | (insert expected) 27 | (purescript-mode) 28 | (turn-on-purescript-indentation) 29 | (indent-region (point-min) (point-max)) 30 | (should (string= expected (buffer-string))))) 31 | 32 | (defun purescript-test-indentation (before after &optional start-line) 33 | (with-temp-buffer 34 | (insert before) 35 | (purescript-mode) 36 | (turn-on-purescript-indentation) 37 | (indent-region (if start-line start-line (point-min)) 38 | (point-max)) 39 | (should (string= after (buffer-string))))) 40 | 41 | (ert-deftest newtype-comma-first () 42 | (purescript-test-indentation " 43 | newtype Foo = Foo { field1 :: MyType 44 | , field2 :: Int 45 | , field3 :: HashMap ParType Foo }" 46 | 47 | " 48 | newtype Foo = Foo { field1 :: MyType 49 | , field2 :: Int 50 | , field3 :: HashMap ParType Foo }")) 51 | 52 | (ert-deftest newtype-comma-end () 53 | (purescript-test-indentation " 54 | newtype Foo = Foo { field1 :: MyType, 55 | field2 :: Int, 56 | field3 :: HashMap ParType Foo }" 57 | 58 | " 59 | newtype Foo = Foo { field1 :: MyType, 60 | field2 :: Int, 61 | field3 :: HashMap ParType Foo }")) 62 | 63 | (ert-deftest data-bar-first () 64 | (purescript-test-indentation " 65 | data Foo = Foo1 Bar 66 | | Foo2 Bar2 67 | | Foo3 Unit" 68 | 69 | " 70 | data Foo = Foo1 Bar 71 | | Foo2 Bar2 72 | | Foo3 Unit")) 73 | 74 | (ert-deftest data-bar-end () 75 | (purescript-test-indentation " 76 | data Foo = Foo1 Bar | 77 | Foo2 Bar2 | 78 | Foo3 Unit" 79 | 80 | " 81 | data Foo = Foo1 Bar | 82 | Foo2 Bar2 | 83 | Foo3 Unit")) 84 | 85 | (ert-deftest imports-zero-indented () 86 | (purescript-test-indentation " 87 | module MyModule where 88 | 89 | import Prelude 90 | 91 | import Data.Array (many) 92 | import Data.Array as Array 93 | import Data.Either (Either(..))" 94 | 95 | " 96 | module MyModule where 97 | 98 | import Prelude 99 | 100 | import Data.Array (many) 101 | import Data.Array as Array 102 | import Data.Either (Either(..))")) 103 | 104 | (ert-deftest imports-indented-forward () 105 | "PureScript allows for imports to have indentation, but the 106 | indentation must be the same. In this test we skip first indented 107 | import, and test that further lines inherit indentation level." 108 | :expected-result :failed 109 | (purescript-test-indentation " 110 | module MyModule where 111 | 112 | import Prelude 113 | 114 | import Data.Array (many) 115 | import Data.Array as Array 116 | import Data.Either (Either(..))" 117 | 118 | " 119 | module MyModule where 120 | 121 | import Prelude 122 | 123 | import Data.Array (many) 124 | import Data.Array as Array 125 | import Data.Either (Either(..))" 126 | 6)) 127 | 128 | (ert-deftest imports-qualified-newline-comma-first () 129 | :expected-result :failed 130 | (purescript-test-indentation " 131 | import Data.List.NonEmpty (NonEmptyList) 132 | import Data.Maybe (Maybe(..) 133 | , fromMaybe)" 134 | 135 | " 136 | import Data.List.NonEmpty (NonEmptyList) 137 | import Data.Maybe ( Maybe(..) 138 | , fromMaybe)")) 139 | 140 | (ert-deftest imports-qualified-newline-comma-end () 141 | :expected-result :failed 142 | (purescript-test-indentation " 143 | import Data.List.NonEmpty (NonEmptyList) 144 | import Data.Maybe (Maybe(..), 145 | fromMaybe)" 146 | 147 | " 148 | import Data.List.NonEmpty (NonEmptyList) 149 | import Data.Maybe (Maybe(..), 150 | fromMaybe)")) 151 | 152 | (ert-deftest do-impl () 153 | (purescript-test-indentation " 154 | main = do 155 | pure unit" 156 | 157 | " 158 | main = do 159 | pure unit")) 160 | 161 | (ert-deftest let-bindings () 162 | :expected-result :failed 163 | (purescript-test-indentation " 164 | main = do 165 | let foo = 1 166 | bar = 2 167 | let 168 | buzz = 1 169 | fuzz = 2 170 | pure unit" 171 | 172 | " 173 | main = do 174 | let foo = 1 175 | bar = 2 176 | let 177 | buzz = 1 178 | fuzz = 2 179 | pure unit")) 180 | 181 | (ert-deftest module-exports-list () 182 | :expected-result :failed 183 | (purescript-test-indentation " 184 | module MyModule ( class A 185 | , b, c 186 | , d) where" 187 | 188 | " 189 | module MyModule ( class A 190 | , b, c 191 | , d) where")) 192 | 193 | (ert-deftest module-exports-next-line () 194 | "Parentheses should get indented to the mode indentation size" 195 | :expected-result :failed 196 | (purescript-test-indentation " 197 | module MyModule 198 | (class A, b, c, d) where" 199 | 200 | " 201 | module MyModule 202 | (class A, b, c, d) where")) 203 | 204 | (ert-deftest keyword-record-values () 205 | "PureScript allows keywords to be part of a Record declaration" 206 | :expected-result :failed 207 | (purescript-test-indentation " 208 | type MyRec = { data :: Number 209 | , where :: Number 210 | , instance :: Number 211 | }" 212 | 213 | " 214 | type MyRec = { data :: Number 215 | , where :: Number 216 | , instance :: Number 217 | }")) 218 | 219 | (ert-deftest func-with-do () 220 | :expected-result :failed 221 | (purescript-test-indentation-expected-only " 222 | foo :: Foo 223 | foo = do 224 | pure unit 225 | ")) 226 | 227 | (ert-deftest do-bindings () 228 | :expected-result :failed 229 | (purescript-test-indentation " 230 | foo :: Foo 231 | foo = do 232 | _ <- something 233 | identifier :: Array String <- function call 234 | _ <- another call 235 | pure unit" 236 | 237 | " 238 | foo :: Foo 239 | foo = do 240 | _ <- something 241 | identifier :: Array String <- function call 242 | _ <- another call 243 | pure unit")) 244 | 245 | (ert-deftest let-in-separate-lines () 246 | "Tests bug #12" 247 | (purescript-test-indentation " 248 | test1 a 249 | = let { x } = a 250 | in x" 251 | 252 | " 253 | test1 a 254 | = let { x } = a 255 | in x")) 256 | 257 | (ert-deftest case-of-separate-lines () 258 | "Tests bug #12" 259 | (purescript-test-indentation " 260 | test3 a 261 | = case a of 262 | { x: y } 263 | -> y" 264 | 265 | " 266 | test3 a 267 | = case a of 268 | { x: y } 269 | -> y")) 270 | 271 | (ert-deftest comma-first-list-after-case-of () 272 | "A comma-first list was getting misindented if goes after case-of" 273 | :expected-result :failed 274 | (purescript-test-indentation-expected-only " 275 | fun = case _ of 276 | [ a 277 | , b ] 278 | ")) 279 | 280 | (ert-deftest multiline-func-decl-arrow-first () 281 | (purescript-test-indentation-expected-only " 282 | foo :: 283 | ∀ a. A 284 | -> B 285 | -> C 286 | ")) 287 | 288 | (ert-deftest multiline-func-decl-arrow-last () 289 | (purescript-test-indentation-expected-only " 290 | foo :: 291 | ∀ a. 292 | A -> 293 | B -> 294 | C 295 | ")) 296 | -------------------------------------------------------------------------------- /purescript-simple-indent.el: -------------------------------------------------------------------------------- 1 | ;;; purescript-simple-indent.el --- Simple indentation module for PureScript Mode -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 1998 Heribert Schuetz, Graeme E Moss 4 | 5 | ;; Author: Heribert Schuetz 6 | ;; Graeme E Moss 7 | ;; Keywords: indentation files PureScript 8 | 9 | ;; This file is not part of GNU Emacs. 10 | 11 | ;; This file is free software; you can redistribute it and/or modify 12 | ;; it under the terms of the GNU General Public License as published by 13 | ;; the Free Software Foundation; either version 3, or (at your option) 14 | ;; any later version. 15 | 16 | ;; This file is distributed in the hope that it will be useful, 17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | ;; GNU General Public License for more details. 20 | 21 | ;; You should have received a copy of the GNU General Public License 22 | ;; along with this program. If not, see . 23 | 24 | ;;; Commentary: 25 | 26 | ;; Purpose: 27 | ;; 28 | ;; To support simple indentation of PureScript scripts. 29 | ;; 30 | ;; 31 | ;; Installation: 32 | ;; 33 | ;; To bind TAB to the indentation command for all PureScript buffers, add 34 | ;; this to .emacs: 35 | ;; 36 | ;; (add-hook 'purescript-mode-hook 'turn-on-purescript-simple-indent) 37 | ;; 38 | ;; Otherwise, call `turn-on-purescript-simple-indent'. 39 | ;; 40 | ;; 41 | ;; Customisation: 42 | ;; 43 | ;; None supported. 44 | ;; 45 | ;; 46 | ;; History: 47 | ;; 48 | ;; If you have any problems or suggestions, after consulting the list 49 | ;; below, email gem@cs.york.ac.uk quoting the version of you are 50 | ;; using, the version of Emacs you are using, and a small example of 51 | ;; the problem or suggestion. 52 | ;; 53 | ;; Version 1.0: 54 | ;; Brought over from PureScript mode v1.1. 55 | ;; 56 | ;; Present Limitations/Future Work (contributions are most welcome!): 57 | ;; 58 | ;; (None so far.) 59 | 60 | ;;; Code: 61 | 62 | ;; All functions/variables start with 63 | ;; `(turn-(on/off)-)purescript-simple-indent'. 64 | 65 | (require 'purescript-mode) 66 | 67 | (defgroup purescript-simple-indent nil 68 | "Simple PureScript indentation." 69 | :link '(custom-manual "(purescript-mode)Indentation") 70 | :group 'purescript 71 | :prefix "purescript-simple-indent-") 72 | 73 | ;; Version. 74 | (defconst purescript-simple-indent-version "1.2" 75 | "`purescript-simple-indent' version number.") 76 | (defun purescript-simple-indent-version () 77 | "Echo the current version of `purescript-simple-indent' in the minibuffer." 78 | (interactive) 79 | (message "Using purescript-simple-indent version %s" 80 | purescript-simple-indent-version)) 81 | 82 | ;; Partly stolen from `indent-relative' in indent.el: 83 | (defun purescript-simple-indent () 84 | "Space out to under next visible indent point. 85 | Indent points are positions of non-whitespace following whitespace in 86 | lines preceeding point. A position is visible if it is to the left of 87 | the first non-whitespace of every nonblank line between the position and 88 | the current line. If there is no visible indent point beyond the current 89 | column, `tab-to-tab-stop' is done instead." 90 | (interactive) 91 | (let* ((start-column (current-column)) 92 | (invisible-from nil) ; `nil' means infinity here 93 | (indent 94 | (catch 'purescript-simple-indent-break 95 | (save-excursion 96 | (while (progn (beginning-of-line) 97 | (not (bobp))) 98 | (forward-line -1) 99 | (if (not (looking-at "[ \t]*\n")) 100 | (let ((this-indentation (current-indentation))) 101 | (if (or (not invisible-from) 102 | (< this-indentation invisible-from)) 103 | (if (> this-indentation start-column) 104 | (setq invisible-from this-indentation) 105 | (let ((end (line-beginning-position 2))) 106 | (move-to-column start-column) 107 | ;; Is start-column inside a tab on this line? 108 | (if (> (current-column) start-column) 109 | (backward-char 1)) 110 | (or (looking-at "[ \t]") 111 | (skip-chars-forward "^ \t" end)) 112 | (skip-chars-forward " \t" end) 113 | (let ((col (current-column))) 114 | (throw 'purescript-simple-indent-break 115 | (if (or (= (point) end) 116 | (and invisible-from 117 | (> col invisible-from))) 118 | invisible-from 119 | col))))))))))))) 120 | (if indent 121 | (let ((opoint (point-marker))) 122 | (indent-line-to indent) 123 | (if (> opoint (point)) 124 | (goto-char opoint)) 125 | (set-marker opoint nil)) 126 | (tab-to-tab-stop)))) 127 | 128 | (defun purescript-simple-indent-backtab () 129 | "Indent backwards. Dual to `purescript-simple-indent'." 130 | (interactive) 131 | (let ((current-point (point)) 132 | (i 0) 133 | (x 0)) 134 | (goto-char (line-beginning-position)) 135 | (save-excursion 136 | (while (< (point) current-point) 137 | (purescript-simple-indent) 138 | (setq i (+ i 1)))) 139 | (while (< x (- i 1)) 140 | (purescript-simple-indent) 141 | (setq x (+ x 1))))) 142 | 143 | (defun purescript-simple-indent-newline-same-col () 144 | "Make a newline and go to the same column as the current line." 145 | (interactive) 146 | (let ((start-end 147 | (save-excursion 148 | (let* ((start (line-beginning-position)) 149 | (end (progn (goto-char start) 150 | (search-forward-regexp 151 | "[^ ]" (line-end-position) t 1)))) 152 | (when end (cons start (1- end))))))) 153 | (if start-end 154 | (progn (newline) 155 | (insert (buffer-substring-no-properties 156 | (car start-end) (cdr start-end)))) 157 | (newline)))) 158 | 159 | (defun purescript-simple-indent-newline-indent () 160 | "Make a newline on the current column and indent on step." 161 | (interactive) 162 | (purescript-simple-indent-newline-same-col) 163 | (insert (make-string purescript-indent-spaces ? ))) 164 | 165 | ;;;###autoload 166 | (define-minor-mode purescript-simple-indent-mode 167 | "Simple PureScript indentation mode that uses simple heuristic. 168 | In this minor mode, `indent-for-tab-command' (bound to by 169 | default) will move the cursor to the next indent point in the 170 | previous nonblank line, whereas `purescript-simple-indent-backtab' 171 | \ (bound to by default) will move the cursor the 172 | previous indent point. An indent point is a non-whitespace 173 | character following whitespace. 174 | 175 | Runs `purescript-simple-indent-hook' on activation." 176 | :lighter " Ind" 177 | :group 'purescript-simple-indent 178 | :keymap '(([backtab] . purescript-simple-indent-backtab)) 179 | (kill-local-variable 'indent-line-function) 180 | (when purescript-simple-indent-mode 181 | (set (make-local-variable 'indent-line-function) 'purescript-simple-indent) 182 | (run-hooks 'purescript-simple-indent-hook))) 183 | 184 | ;; The main functions. 185 | ;;;###autoload 186 | (defun turn-on-purescript-simple-indent () 187 | "Turn on function `purescript-simple-indent-mode'." 188 | (interactive) 189 | (purescript-simple-indent-mode)) 190 | 191 | (defun turn-off-purescript-simple-indent () 192 | "Turn off function `purescript-simple-indent-mode'." 193 | (interactive) 194 | (purescript-simple-indent-mode 0)) 195 | 196 | ;; Provide ourselves: 197 | 198 | (provide 'purescript-simple-indent) 199 | 200 | ;;; purescript-simple-indent.el ends here 201 | -------------------------------------------------------------------------------- /purescript-unicode-input-method.el: -------------------------------------------------------------------------------- 1 | ;;; purescript-unicode-input-method.el --- PureScript Unicode helper functions -*- coding: utf-8 lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2010-2011 Roel van Dijk 4 | 5 | ;; Author: Roel van Dijk 6 | 7 | ;; This file is not part of GNU Emacs. 8 | 9 | ;; This file is free software; you can redistribute it and/or modify 10 | ;; it under the terms of the GNU General Public License as published by 11 | ;; the Free Software Foundation; either version 3 of the License, or 12 | ;; (at your option) any later version. 13 | 14 | ;; This file is distributed in the hope that it will be useful, 15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | ;; GNU General Public License for more details. 18 | 19 | ;; You should have received a copy of the GNU General Public License 20 | ;; along with this program. If not, see . 21 | 22 | ;;; Commentary: 23 | 24 | ;;; Code: 25 | 26 | (require 'quail) 27 | 28 | ;;;###autoload 29 | (defun turn-on-purescript-unicode-input-method () 30 | "Set input method `purescript-unicode'. 31 | See Info node `Unicode(purescript-mode)' for more details." 32 | (interactive) 33 | (set-input-method "purescript-unicode")) 34 | 35 | (quail-define-package 36 | "purescript-unicode" ;; name 37 | "UTF-8" ;; language 38 | "\\" ;; title 39 | t ;; guidance 40 | "PureScript Unicode input method. 41 | Designed to be used with the PureScript UnicodeSyntax language 42 | extension in combination with the x-unicode-symbols set of 43 | packages (base-unicode-symbols and containers-unicode-symbols). 44 | " ;; docstring 45 | nil ;; translation-keys 46 | nil ;; forget-last-selection 47 | nil ;; deterministic 48 | nil ;; kbd-translate 49 | nil ;; show-layout 50 | nil ;; create-decode-map 51 | nil ;; maximum-shortest 52 | nil ;; overlay-plist 53 | nil ;; update-translation-function 54 | nil ;; conversion-keys 55 | t ;; simple 56 | ) 57 | 58 | (quail-define-rules 59 | ;; Greek letters 60 | ("alpha " ["α"]) 61 | ("Alpha " ["Α"]) 62 | ("beta " ["β"]) 63 | ("Beta " ["Β"]) 64 | ("gamma " ["γ"]) 65 | ("Gamma " ["Γ"]) 66 | ("delta " ["δ"]) 67 | ("Delta " ["Δ"]) 68 | ("epsilon " ["ε"]) 69 | ("Epsilon " ["Ε"]) 70 | ("zeta " ["ζ"]) 71 | ("Zeta " ["Ζ"]) 72 | ("eta " ["η"]) 73 | ("Eta " ["Η"]) 74 | ("theta " ["θ"]) 75 | ("Theta " ["Θ"]) 76 | ("iota " ["ι"]) 77 | ("Iota " ["Ι"]) 78 | ("kappa " ["κ"]) 79 | ("Kappa " ["Κ"]) 80 | ("lambda " ["λ"]) 81 | ("Lambda " ["Λ"]) 82 | ("lamda " ["λ"]) 83 | ("Lamda " ["Λ"]) 84 | ("mu " ["μ"]) 85 | ("Mu " ["Μ"]) 86 | ("nu " ["ν"]) 87 | ("Nu " ["Ν"]) 88 | ("xi " ["ξ"]) 89 | ("Xi " ["Ξ"]) 90 | ("omicron " ["ο"]) 91 | ("Omicron " ["Ο"]) 92 | ("pi " ["π"]) 93 | ("Pi " ["Π"]) 94 | ("rho " ["ρ"]) 95 | ("Rho " ["Ρ"]) 96 | ("sigma " ["σ"]) 97 | ("Sigma " ["Σ"]) 98 | ("tau " ["τ"]) 99 | ("Tau " ["Τ"]) 100 | ("upsilon " ["υ"]) 101 | ("Upsilon " ["Υ"]) 102 | ("phi " ["φ"]) 103 | ("Phi " ["Φ"]) 104 | ("chi " ["χ"]) 105 | ("Chi " ["Χ"]) 106 | ("psi " ["ψ"]) 107 | ("Psi " ["Ψ"]) 108 | ("omega " ["ω"]) 109 | ("Omega " ["Ω"]) 110 | ("digamma " ["ϝ"]) 111 | ("Digamma " ["Ϝ"]) 112 | ("san " ["ϻ"]) 113 | ("San " ["Ϻ"]) 114 | ("qoppa " ["ϙ"]) 115 | ("Qoppa " ["Ϙ"]) 116 | ("sampi " ["ϡ"]) 117 | ("Sampi " ["Ϡ"]) 118 | ("stigma " ["ϛ"]) 119 | ("Stigma " ["Ϛ"]) 120 | ("heta " ["ͱ"]) 121 | ("Heta " ["Ͱ"]) 122 | ("sho " ["ϸ"]) 123 | ("Sho " ["Ϸ"]) 124 | 125 | ;; Double-struck letters 126 | ("|A|" ["𝔸"]) 127 | ("|B|" ["𝔹"]) 128 | ("|C|" ["ℂ"]) 129 | ("|D|" ["𝔻"]) 130 | ("|E|" ["𝔼"]) 131 | ("|F|" ["𝔽"]) 132 | ("|G|" ["𝔾"]) 133 | ("|H|" ["ℍ"]) 134 | ("|I|" ["𝕀"]) 135 | ("|J|" ["𝕁"]) 136 | ("|K|" ["𝕂"]) 137 | ("|L|" ["𝕃"]) 138 | ("|M|" ["𝕄"]) 139 | ("|N|" ["ℕ"]) 140 | ("|O|" ["𝕆"]) 141 | ("|P|" ["ℙ"]) 142 | ("|Q|" ["ℚ"]) 143 | ("|R|" ["ℝ"]) 144 | ("|S|" ["𝕊"]) 145 | ("|T|" ["𝕋"]) 146 | ("|U|" ["𝕌"]) 147 | ("|V|" ["𝕍"]) 148 | ("|W|" ["𝕎"]) 149 | ("|X|" ["𝕏"]) 150 | ("|Y|" ["𝕐"]) 151 | ("|Z|" ["ℤ"]) 152 | ("|gamma|" ["ℽ"]) 153 | ("|Gamma|" ["ℾ"]) 154 | ("|pi|" ["ℼ"]) 155 | ("|Pi|" ["ℿ"]) 156 | 157 | ;; Types 158 | ("::" ["∷"]) 159 | 160 | ;; Quantifiers 161 | ("forall" ["∀"]) 162 | ("exists" ["∃"]) 163 | 164 | ;; Arrows 165 | ("->" ["→"]) 166 | ;; ("-->" ["⟶"]) 167 | ("<-" ["←"]) 168 | ;; ("<--" ["⟵"]) 169 | ;; ("<->" ["↔"]) 170 | ;; ("<-->" ["⟷"]) 171 | 172 | ("=>" ["⇒"]) 173 | ;; ("==>" ["⟹"]) 174 | ;; ("<=" ["⇐"]) 175 | ;; ("<==" ["⟸"]) 176 | ;; ("<=>" ["⇔"]) 177 | ;; ("<==>" ["⟺"]) 178 | 179 | ;; ("|->" ["↦"]) 180 | ;; ("|-->" ["⟼"]) 181 | ;; ("<-|" ["↤"]) 182 | ;; ("<--|" ["⟻"]) 183 | 184 | ;; ("|=>" ["⤇"]) 185 | ;; ("|==>" ["⟾"]) 186 | ;; ("<=|" ["⤆"]) 187 | ;; ("<==|" ["⟽"]) 188 | 189 | ("~>" ["⇝"]) 190 | ;; ("~~>" ["⟿"]) 191 | ("<~" ["⇜"]) 192 | ;; ("<~~" ["⬳"]) 193 | 194 | ;; (">->" ["↣"]) 195 | ;; ("<-<" ["↢"]) 196 | ;; ("->>" ["↠"]) 197 | ;; ("<<-" ["↞"]) 198 | 199 | ;; (">->>" ["⤖"]) 200 | ;; ("<<-<" ["⬻"]) 201 | 202 | ;; ("<|-" ["⇽"]) 203 | ;; ("-|>" ["⇾"]) 204 | ;; ("<|-|>" ["⇿"]) 205 | 206 | ;; ("<-/-" ["↚"]) 207 | ;; ("-/->" ["↛"]) 208 | 209 | ;; ("<-|-" ["⇷"]) 210 | ;; ("-|->" ["⇸"]) 211 | ;; ("<-|->" ["⇹"]) 212 | 213 | ;; ("<-||-" ["⇺"]) 214 | ;; ("-||->" ["⇻"]) 215 | ;; ("<-||->" ["⇼"]) 216 | 217 | ;; ("-o->" ["⇴"]) 218 | ;; ("<-o-" ["⬰"]) 219 | 220 | ;; Boolean operators 221 | ;; ("not" ["¬"]) 222 | ("&&" ["∧"]) 223 | ("||" ["∨"]) 224 | 225 | ;; Relational operators 226 | ("==" ["≡"]) 227 | ("/=" ["≢" "≠"]) 228 | ("<=" ["≤"]) 229 | (">=" ["≥"]) 230 | ("/<" ["≮"]) 231 | ("/>" ["≯"]) 232 | 233 | ;; Arithmetic 234 | ;; (" / " [" ÷ "]) 235 | (" * " [" ⋅ "]) 236 | 237 | ;; Containers / Collections 238 | ;; ("++" ["⧺"]) 239 | ;; ("+++" ["⧻"]) 240 | ;; ("|||" ["⫴"]) 241 | ;; ("empty" ["∅"]) 242 | ("elem" ["∈"]) 243 | ("notElem" ["∉"]) 244 | ("member" ["∈"]) 245 | ("notMember" ["∉"]) 246 | ("union" ["∪"]) 247 | ("intersection" ["∩"]) 248 | ("isSubsetOf" ["⊆"]) 249 | ("isProperSubsetOf" ["⊂"]) 250 | 251 | ;; Other 252 | ;; ("<<" ["≪"]) 253 | ;; (">>" ["≫"]) 254 | ;; ("<<<" ["⋘"]) 255 | ;; (">>>" ["⋙"]) 256 | ("<|" ["⊲"]) 257 | ("|>" ["⊳"]) 258 | ("><" ["⋈"]) 259 | ;; ("mempty" ["∅"]) 260 | ("mappend" ["⊕"]) 261 | ;; ("<*>" ["⊛"]) 262 | (" . " [" ∘ "]) 263 | ("undefined" ["⊥"]) 264 | (":=" ["≔"]) 265 | ("=:" ["≕"]) 266 | ("=def" ["≝"]) 267 | ("=?" ["≟"]) 268 | ("..." ["…"]) 269 | 270 | ;; Braces 271 | ;; ("[|" ["〚"]) 272 | ;; ("|]" ["〛"]) 273 | 274 | ;; Numeric subscripts 275 | ("_0 " ["₀"]) 276 | ("_1 " ["₁"]) 277 | ("_2 " ["₂"]) 278 | ("_3 " ["₃"]) 279 | ("_4 " ["₄"]) 280 | ("_5 " ["₅"]) 281 | ("_6 " ["₆"]) 282 | ("_7 " ["₇"]) 283 | ("_8 " ["₈"]) 284 | ("_9 " ["₉"]) 285 | 286 | ;; Numeric superscripts 287 | ("^0 " ["⁰"]) 288 | ("^1 " ["¹"]) 289 | ("^2 " ["²"]) 290 | ("^3 " ["³"]) 291 | ("^4 " ["⁴"]) 292 | ("^5 " ["⁵"]) 293 | ("^6 " ["⁶"]) 294 | ("^7 " ["⁷"]) 295 | ("^8 " ["⁸"]) 296 | ("^9 " ["⁹"]) 297 | ) 298 | 299 | (provide 'purescript-unicode-input-method) 300 | 301 | ;;; purescript-unicode-input-method.el ends here 302 | -------------------------------------------------------------------------------- /purescript-align-imports.el: -------------------------------------------------------------------------------- 1 | ;;; purescript-align-imports.el --- Align the import lines in a PureScript file -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2010 Chris Done 4 | 5 | ;; Author: Chris Done 6 | 7 | ;; This file is not part of GNU Emacs. 8 | 9 | ;; This program is free software: you can redistribute it and/or 10 | ;; modify it under the terms of the GNU General Public License as 11 | ;; published by the Free Software Foundation, either version 3 of 12 | ;; the License, or (at your option) any later version. 13 | 14 | ;; This program is distributed in the hope that it will be 15 | ;; useful, but WITHOUT ANY WARRANTY; without even the implied 16 | ;; warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 17 | ;; PURPOSE. See the GNU General Public License for more details. 18 | 19 | ;; You should have received a copy of the GNU General Public 20 | ;; License along with this program. If not, see 21 | ;; . 22 | 23 | ;;; Commentary: 24 | 25 | ;; Consider the following imports list: 26 | ;; 27 | ;; import One 28 | ;; import Two as A 29 | ;; import qualified Three 30 | ;; import qualified Four as PRELUDE 31 | ;; import Five (A) 32 | ;; import Six (A,B) 33 | ;; import qualified Seven (A,B) 34 | ;; import "abc" Eight 35 | ;; import "abc" Nine as TWO 36 | ;; import qualified "abc" Ten 37 | ;; import qualified "defg" Eleven as PRELUDE 38 | ;; import "barmu" Twelve (A) 39 | ;; import "zotconpop" Thirteen (A,B) 40 | ;; import qualified "z" Fourteen (A,B) 41 | ;; import Fifteen hiding (A) 42 | ;; import Sixteen as TWO hiding (A) 43 | ;; import qualified Seventeen hiding (A) 44 | ;; import qualified Eighteen as PRELUDE hiding (A) 45 | ;; import "abc" Nineteen hiding (A) 46 | ;; import "abc" Twenty as TWO hiding (A) 47 | ;; 48 | ;; When purescript-align-imports is run within the same buffer, the 49 | ;; import list is transformed to: 50 | ;; 51 | ;; import "abc" Eight 52 | ;; import qualified Eighteen as PRELUDE hiding (A) 53 | ;; import qualified "defg" Eleven as PRELUDE 54 | ;; import Fifteen hiding (A) 55 | ;; import Five (A) 56 | ;; import qualified Four as PRELUDE 57 | ;; import qualified "z" Fourteen (A,B) 58 | ;; import "abc" Nine as TWO 59 | ;; import "abc" Nineteen hiding (A) 60 | ;; import One 61 | ;; import qualified Seven (A,B) 62 | ;; import qualified Seventeen hiding (A) 63 | ;; import Six (A,B) 64 | ;; import Sixteen as TWO hiding (A) 65 | ;; import qualified "abc" Ten 66 | ;; import "zotconpop" Thirteen (A,B) 67 | ;; import qualified Three 68 | ;; import "barmu" Twelve (A) 69 | ;; import "abc" Twenty as TWO hiding (A) 70 | ;; import Two as A 71 | ;; 72 | ;; If you want everything after module names to be padded out, too, 73 | ;; customize `purescript-align-imports-pad-after-name', and you'll get: 74 | ;; 75 | ;; import One 76 | ;; import Two as A 77 | ;; import qualified Three 78 | ;; import qualified Four as PRELUDE 79 | ;; import Five (A) 80 | ;; import Six (A,B) 81 | ;; import qualified Seven (A,B) 82 | ;; import "abc" Eight 83 | ;; import "abc" Nine as TWO 84 | ;; import qualified "abc" Ten 85 | ;; import qualified "defg" Eleven as PRELUDE 86 | ;; import "barmu" Twelve (A) 87 | ;; import "zotconpop" Thirteen (A,B) 88 | ;; import qualified "z" Fourteen (A,B) 89 | ;; import Fifteen hiding (A) 90 | ;; import Sixteen as TWO hiding (A) 91 | ;; import qualified Seventeen hiding (A) 92 | ;; import qualified Eighteen as PRELUDE hiding (A) 93 | ;; import "abc" Nineteen hiding (A) 94 | ;; import "abc" Twenty as TWO hiding (A) 95 | 96 | ;;; Code: 97 | 98 | (require 'cl-lib) 99 | 100 | (defvar purescript-align-imports-regexp 101 | (concat "^\\(import[ ]+\\)" 102 | "\\(qualified \\)?" 103 | "[ ]*\\(\"[^\"]*\" \\)?" 104 | "[ ]*\\([A-Za-z0-9_.']+\\)" 105 | "[ ]*\\([ ]*as [A-Z][^ ]*\\)?" 106 | "[ ]*\\((.*)\\)?" 107 | "\\([ ]*hiding (.*)\\)?" 108 | "\\( -- .*\\)?[ ]*$") 109 | "Regex used for matching components of an import.") 110 | 111 | (defcustom purescript-align-imports-pad-after-name 112 | nil 113 | "Pad layout after the module name also." 114 | :type 'boolean 115 | :group 'purescript-interactive) 116 | 117 | ;;;###autoload 118 | (defun purescript-align-imports () 119 | "Align all the imports in the buffer." 120 | (interactive) 121 | (when (purescript-align-imports-line-match) 122 | (save-excursion 123 | (goto-char (point-min)) 124 | (let* ((imports (purescript-align-imports-collect)) 125 | (padding (purescript-align-imports-padding imports))) 126 | (mapc (lambda (x) 127 | (goto-char (cdr x)) 128 | (delete-region (point) (line-end-position)) 129 | (insert (purescript-align-imports-chomp 130 | (purescript-align-imports-fill padding (car x))))) 131 | imports)))) 132 | nil) 133 | 134 | (defun purescript-align-imports-line-match () 135 | "Try to match the current line as a regexp." 136 | (let ((line (buffer-substring-no-properties (line-beginning-position) 137 | (line-end-position)))) 138 | (if (string-match "^import " line) 139 | line 140 | nil))) 141 | 142 | (defun purescript-align-imports-collect () 143 | "Collect a list of mark / import statement pairs." 144 | (let ((imports '())) 145 | (while (not (or (equal (point) (point-max)) (purescript-align-imports-after-imports-p))) 146 | (let ((line (purescript-align-imports-line-match-it))) 147 | (when line 148 | (let ((match 149 | (purescript-align-imports-merge-parts 150 | (cl-loop for i from 1 to 8 151 | collect (purescript-align-imports-chomp (match-string i line)))))) 152 | (setq imports (cons (cons match (line-beginning-position)) 153 | imports))))) 154 | (forward-line)) 155 | imports)) 156 | 157 | (defun purescript-align-imports-merge-parts (l) 158 | "Merge together parts of an import statement that shouldn't be separated." 159 | (let ((parts (apply #'vector l)) 160 | (join (lambda (ls) 161 | (cl-reduce (lambda (a b) 162 | (concat a 163 | (if (and (> (length a) 0) 164 | (> (length b) 0)) 165 | " " 166 | "") 167 | b)) 168 | ls)))) 169 | (if purescript-align-imports-pad-after-name 170 | (list (funcall join (list (aref parts 0) 171 | (aref parts 1) 172 | (aref parts 2))) 173 | (aref parts 3) 174 | (funcall join (list (aref parts 4) 175 | (aref parts 5) 176 | (aref parts 6))) 177 | (aref parts 7)) 178 | (list (funcall join (list (aref parts 0) 179 | (aref parts 1) 180 | (aref parts 2))) 181 | (funcall join (list (aref parts 3) 182 | (aref parts 4) 183 | (aref parts 5) 184 | (aref parts 6) 185 | (aref parts 7))))))) 186 | 187 | (defun purescript-align-imports-chomp (str) 188 | "Chomp leading and tailing whitespace from STR." 189 | (if str 190 | (replace-regexp-in-string "\\(^[[:space:]\n]*\\|[[:space:]\n]*$\\)" "" 191 | str) 192 | "")) 193 | 194 | (defun purescript-align-imports-padding (imports) 195 | "Find the padding for each part of the import statements." 196 | (if (null imports) 197 | imports 198 | (cl-reduce (lambda (a b) (cl-mapcar #'max a b)) 199 | (mapcar (lambda (x) (mapcar #'length (car x))) 200 | imports)))) 201 | 202 | (defun purescript-align-imports-fill (padding line) 203 | "Fill an import line using the padding worked out from all statements." 204 | (mapconcat #'identity 205 | (cl-mapcar (lambda (pad part) 206 | (if (> (length part) 0) 207 | (concat part (make-string (- pad (length part)) ? )) 208 | (make-string pad ? ))) 209 | padding 210 | line) 211 | " ")) 212 | 213 | (defun purescript-align-imports-line-match-it () 214 | "Try to match the current line as a regexp." 215 | (let ((line (buffer-substring-no-properties (line-beginning-position) 216 | (line-end-position)))) 217 | (if (string-match purescript-align-imports-regexp line) 218 | line 219 | nil))) 220 | 221 | (defun purescript-align-imports-after-imports-p () 222 | "Are we after the imports list?" 223 | (save-excursion 224 | (goto-char (line-beginning-position)) 225 | (not (not (search-forward-regexp "\\( = \\|\\\\| :: \\)" 226 | (line-end-position) t 1))))) 227 | 228 | (provide 'purescript-align-imports) 229 | 230 | ;;; purescript-align-imports.el ends here 231 | -------------------------------------------------------------------------------- /purescript-font-lock.el: -------------------------------------------------------------------------------- 1 | ;;; purescript-font-lock.el --- Font locking module for PureScript Mode -*- lexical-binding: t -*- 2 | 3 | ;; Copyright 2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc. 4 | ;; Copyright 1997-1998 Graeme E Moss, and Tommy Thorn 5 | 6 | ;; Author: 1997-1998 Graeme E Moss 7 | ;; 1997-1998 Tommy Thorn 8 | ;; 2003 Dave Love 9 | ;; Keywords: faces files PureScript 10 | 11 | ;; This file is not part of GNU Emacs. 12 | 13 | ;; This file is free software; you can redistribute it and/or modify 14 | ;; it under the terms of the GNU General Public License as published by 15 | ;; the Free Software Foundation; either version 3, or (at your option) 16 | ;; any later version. 17 | 18 | ;; This file is distributed in the hope that it will be useful, 19 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | ;; GNU General Public License for more details. 22 | 23 | ;; You should have received a copy of the GNU General Public License 24 | ;; along with this program. If not, see . 25 | 26 | ;;; Commentary: 27 | 28 | ;; Purpose: 29 | ;; 30 | ;; To support fontification of standard PureScript keywords, symbols, 31 | ;; functions, etc. Supports full PureScript 1.4 as well as LaTeX- and 32 | ;; Bird-style literate scripts. 33 | ;; 34 | ;; Customisation: 35 | ;; 36 | ;; Two levels of fontification are defined: level one (the default) 37 | ;; and level two (more colour). The former does not colour operators. 38 | ;; Use the variable `font-lock-maximum-decoration' to choose 39 | ;; non-default levels of fontification. For example, adding this to 40 | ;; .emacs: 41 | ;; 42 | ;; (setq font-lock-maximum-decoration \\='((purescript-mode . 2) (t . 0))) 43 | ;; 44 | ;; uses level two fontification for `purescript-mode' and default level for all 45 | ;; other modes. See documentation on this variable for further details. 46 | ;; 47 | ;; To alter an attribute of a face, add a hook. For example, to change the 48 | ;; foreground colour of comments to brown, add the following line to .emacs: 49 | ;; 50 | ;; (add-hook \\='purescript-font-lock-hook 51 | ;; (lambda () 52 | ;; (set-face-foreground \\='purescript-comment-face \"brown\"))) 53 | ;; 54 | ;; Note that the colours available vary from system to system. To see what 55 | ;; colours are available on your system, call `list-colors-display' from emacs. 56 | ;; 57 | ;; Bird-style literate PureScript scripts are supported: If the value of 58 | ;; `purescript-literate-bird-style' (automatically set by the PureScript mode of 59 | ;; Moss&Thorn) is non-nil, a Bird-style literate script is assumed. 60 | ;; 61 | ;; Present Limitations/Future Work (contributions are most welcome!): 62 | ;; 63 | ;; . Debatable whether `()' `[]' `(->)' `(,)' `(,,)' etc. should be 64 | ;; highlighted as constructors or not. Should the `->' in 65 | ;; `id :: a -> a' be considered a constructor or a keyword? If so, 66 | ;; how do we distinguish this from `\x -> x'? What about the `\'? 67 | 68 | ;;; Code: 69 | 70 | (require 'font-lock) 71 | (require 'cl-lib) 72 | (require 'purescript-vars) 73 | 74 | (defcustom purescript-font-lock-prettify-symbols-alist 75 | `(("/\\" . ,(decode-char 'ucs #X2227)) 76 | ("\\" . ,(decode-char 'ucs 955)) 77 | ("not" . ,(decode-char 'ucs 172)) 78 | ("->" . ,(decode-char 'ucs 8594)) 79 | ("<-" . ,(decode-char 'ucs 8592)) 80 | ("=>" . ,(decode-char 'ucs 8658)) 81 | ("()" . ,(decode-char 'ucs #X2205)) 82 | ("==" . ,(decode-char 'ucs #X2261)) 83 | ("<<<" . ,(decode-char 'ucs 9675)) 84 | ("/=" . ,(decode-char 'ucs #X2262)) 85 | (">=" . ,(decode-char 'ucs #X2265)) 86 | ("<=" . ,(decode-char 'ucs #X2264)) 87 | ("!!" . ,(decode-char 'ucs #X203C)) 88 | ("&&" . ,(decode-char 'ucs #X2227)) 89 | ("||" . ,(decode-char 'ucs #X2228)) 90 | ("sqrt" . ,(decode-char 'ucs #X221A)) 91 | ("undefined" . ,(decode-char 'ucs #X22A5)) 92 | ("pi" . ,(decode-char 'ucs #X3C0)) 93 | ("~>" . ,(decode-char 'ucs 8669)) ;; Omega language 94 | ("-<" . ,(decode-char 'ucs 8610)) ;; Paterson's arrow syntax 95 | ("::" . ,(decode-char 'ucs 8759)) 96 | ("forall" . ,(decode-char 'ucs 8704))) 97 | "A set of symbol compositions for use as `prettify-symbols-alist'." 98 | :group 'purescript 99 | :type '(repeat (cons string character))) 100 | 101 | ;; Use new vars for the font-lock faces. The indirection allows people to 102 | ;; use different faces than in other modes, as before. 103 | (defvar purescript-keyword-face 'font-lock-keyword-face) 104 | (defvar purescript-constructor-face 'font-lock-type-face) 105 | ;; This used to be `font-lock-variable-name-face' but it doesn't result in 106 | ;; a highlighting that's consistent with other modes (it's mostly used 107 | ;; for function defintions). 108 | (defvar purescript-definition-face 'font-lock-function-name-face) 109 | ;; This is probably just wrong, but it used to use 110 | ;; `font-lock-function-name-face' with a result that was not consistent with 111 | ;; other major modes, so I just exchanged with `purescript-definition-face'. 112 | (defvar purescript-operator-face 'font-lock-builtin-face) 113 | (defvar purescript-default-face nil) 114 | (defvar purescript-literate-comment-face 'font-lock-doc-face 115 | "Face with which to fontify literate comments. 116 | Set to `default' to avoid fontification of them.") 117 | 118 | ;; The font lock regular expressions. 119 | (defun purescript-font-lock-keywords-create (literate) 120 | "Create fontification definitions for PureScript scripts. 121 | Returns keywords suitable for `font-lock-keywords'." 122 | (let* (;; Bird-style literate scripts start a line of code with 123 | ;; "^>", otherwise a line of code starts with "^". 124 | (line-prefix (if (eq literate 'bird) "^> ?" "^")) 125 | 126 | ;; Most names are borrowed from the lexical syntax of the PureScript 127 | ;; report. 128 | ;; Some of these definitions have been superseded by using the 129 | ;; syntax table instead. 130 | 131 | ;; (ASCsymbol "-!#$%&*+./<=>?@\\\\^|~") 132 | ;; Put the minus first to make it work in ranges. 133 | 134 | ;; We allow _ as the first char to fit GHC 135 | (varid "\\b[[:lower:]_][[:alnum:]'_]*\\b") 136 | ;; We allow ' preceding conids because of DataKinds/PolyKinds 137 | (conid "\\b'?[[:upper:]][[:alnum:]'_]*\\b") 138 | (modid (concat "\\b" conid "\\(\\." conid "\\)*\\b")) 139 | (qvarid (concat modid "\\." varid)) 140 | (qconid (concat modid "\\." conid)) 141 | (sym 142 | ;; We used to use the below for non-Emacs21, but I think the 143 | ;; regexp based on syntax works for other emacsen as well. -- Stef 144 | ;; (concat "[" symbol ":]+") 145 | ;; Add backslash to the symbol-syntax chars. This seems to 146 | ;; be thrown for some reason by backslash's escape syntax. 147 | "\\(\\s_\\|\\\\\\)+") 148 | 149 | (operator 150 | (concat "\\S_" 151 | ;; All punctuation, excluding (),;[]{}_"'` 152 | "\\([!@#$%^&*+\\-./<=>?@|~:∷\\\\]+\\)" 153 | "\\S_")) 154 | ;; These are only keywords when appear at top-level, optionally with 155 | ;; indentation. They are not reserved and in other levels would represent 156 | ;; record fields or other identifiers. 157 | (toplevel-keywords 158 | (rx line-start (zero-or-more whitespace) 159 | (group (or "type" "import" "data" "class" "newtype" 160 | "instance" "derive") 161 | word-end))) 162 | ;; Reserved identifiers 163 | (reservedid 164 | ;; `as', `hiding', and `qualified' are part of the import 165 | ;; spec syntax, but they are not reserved. 166 | ;; `_' can go in here since it has temporary word syntax. 167 | (regexp-opt 168 | '("ado" "case" "do" "else" "if" "in" "infix" "module" 169 | "infixl" "infixr" "let" "of" "then" "where" "_") 'words)) 170 | 171 | ;; Top-level declarations 172 | (topdecl-var 173 | (concat line-prefix "\\(" varid "\\)\\s-*" 174 | ;; optionally allow for a single newline after identifier 175 | ;; NOTE: not supported for bird-style .lpurs files 176 | (if (eq literate 'bird) nil "\\([\n]\\s-+\\)?") 177 | ;; A toplevel declaration can be followed by a definition 178 | ;; (=), a type (::) or (∷), a guard, or a pattern which can 179 | ;; either be a variable, a constructor, a parenthesized 180 | ;; thingy, or an integer or a string. 181 | "\\(" varid "\\|" conid "\\|::\\|∷\\|=\\||\\|\\s(\\|[0-9\"']\\)")) 182 | (topdecl-var2 183 | (concat line-prefix "\\(" varid "\\|" conid "\\)\\s-*`\\(" varid "\\)`")) 184 | (topdecl-sym 185 | (concat line-prefix "\\(" varid "\\|" conid "\\)\\s-*\\(" sym "\\)")) 186 | (topdecl-sym2 (concat line-prefix "(\\(" sym "\\))"))) 187 | 188 | `(;; NOTICE the ordering below is significant 189 | ;; 190 | (,toplevel-keywords 1 (symbol-value 'purescript-keyword-face)) 191 | (,reservedid 1 (symbol-value 'purescript-keyword-face)) 192 | (,operator 1 (symbol-value 'purescript-operator-face)) 193 | ;; Special case for `as', `hiding', `safe' and `qualified', which are 194 | ;; keywords in import statements but are not otherwise reserved. 195 | ("\\\\)[ \t]*\\)?\\(?:\\(qualified\\>\\)[ \t]*\\)?[^ \t\n()]+[ \t]*\\(?:\\(\\\\)[ \t]*[^ \t\n()]+[ \t]*\\)?\\(\\\\)?" 196 | (1 (symbol-value 'purescript-keyword-face) nil lax) 197 | (2 (symbol-value 'purescript-keyword-face) nil lax) 198 | (3 (symbol-value 'purescript-keyword-face) nil lax) 199 | (4 (symbol-value 'purescript-keyword-face) nil lax)) 200 | 201 | ;; Case for `foreign import' 202 | (,(rx line-start (0+ whitespace) 203 | (group "foreign") (1+ whitespace) (group "import") word-end) 204 | (1 (symbol-value 'purescript-keyword-face) nil lax) 205 | (2 (symbol-value 'purescript-keyword-face) nil lax)) 206 | 207 | ;; Toplevel Declarations. 208 | ;; Place them *before* generic id-and-op highlighting. 209 | (,topdecl-var (1 (symbol-value 'purescript-definition-face))) 210 | (,topdecl-var2 (2 (symbol-value 'purescript-definition-face))) 211 | (,topdecl-sym (2 (symbol-value 'purescript-definition-face))) 212 | (,topdecl-sym2 (1 (symbol-value 'purescript-definition-face))) 213 | 214 | ;; This one is debatable… 215 | ("\\[\\]" 0 (symbol-value 'purescript-constructor-face)) 216 | ;; Expensive. 217 | (,qvarid 0 (symbol-value 'purescript-default-face)) 218 | (,qconid 0 (symbol-value 'purescript-constructor-face)) 219 | (,(concat "`" varid "`") 0 (symbol-value 'purescript-operator-face)) 220 | ;; Expensive. 221 | (,conid 0 (symbol-value 'purescript-constructor-face)) 222 | 223 | ;; Very expensive. 224 | (,sym 0 (if (eq (char-after (match-beginning 0)) ?:) 225 | purescript-constructor-face 226 | purescript-operator-face))))) 227 | 228 | (defconst purescript-basic-syntactic-keywords 229 | '(;; Character constants (since apostrophe can't have string syntax). 230 | ;; Beware: do not match something like 's-}' or '\n"+' since the first ' 231 | ;; might be inside a comment or a string. 232 | ;; This still gets fooled with "'"'"'"'"'"', but ... oh well. 233 | ("\\Sw\\('\\)\\([^\\'\n]\\|\\\\.[^\\'\n \"}]*\\)\\('\\)" (1 "|") (3 "|")) 234 | ;; The \ is not escaping in \(x,y) -> x + y. 235 | ("\\(\\\\\\)(" (1 ".")) 236 | ;; The second \ in a gap does not quote the subsequent char. 237 | ;; It's probably not worth the trouble, tho. 238 | ;; ("^[ \t]*\\(\\\\\\)" (1 ".")) 239 | ;; Deal with instances of `--' which don't form a comment 240 | ("\\s_\\{3,\\}" (0 (cond ((numberp (nth 4 (syntax-ppss))) 241 | ;; There are no such instances inside nestable comments 242 | nil) 243 | ((string-match "\\`-*|?\\'" (match-string 0)) 244 | ;; Sequence of hyphens. Do nothing in 245 | ;; case of things like `{---'. 246 | nil) 247 | (t "_")))) ; other symbol sequence 248 | )) 249 | 250 | (defconst purescript-bird-syntactic-keywords 251 | (cons '("^[^\n>]" (0 "<")) 252 | purescript-basic-syntactic-keywords)) 253 | 254 | (defconst purescript-latex-syntactic-keywords 255 | (append 256 | '(("^\\\\begin{code}\\(\n\\)" 1 "!") 257 | ;; Note: buffer is widened during font-locking. 258 | ("\\`\\(.\\|\n\\)" (1 "!")) ; start comment at buffer start 259 | ("^\\(\\\\\\)end{code}$" 1 "!")) 260 | purescript-basic-syntactic-keywords)) 261 | 262 | (defun purescript-syntactic-face-function (state) 263 | "`font-lock-syntactic-face-function' for PureScript." 264 | (cond 265 | ((nth 3 state) 'font-lock-string-face) ; as normal 266 | ;; Else comment. If it's from syntax table, use default face. 267 | ((or (eq 'syntax-table (nth 7 state)) 268 | (and (eq purescript-literate 'bird) 269 | (memq (char-before (nth 8 state)) '(nil ?\n)))) 270 | purescript-literate-comment-face) 271 | ;; Try and recognize docstring comments. From what I gather from its 272 | ;; documentation, its comments can take the following forms: 273 | ;; a) {-| ... -} 274 | ;; b) {-^ ... -} 275 | ;; c) -- | ... 276 | ;; d) -- ^ ... 277 | 278 | ;; Worth pointing out purescript opted out of ability to continue 279 | ;; docs-comment by omitting an empty line like in Haskell, see: 280 | ;; https://github.com/purescript/documentation/blob/master/language/Syntax.md 281 | ;; IOW, given a `-- | foo' line followed by `-- bar' line, the latter is a 282 | ;; plain comment. 283 | ((save-excursion 284 | (goto-char (nth 8 state)) 285 | (looking-at "\\(--\\|{-\\)[ \\t]*[|^]")) 286 | 'font-lock-doc-face) 287 | (t 'font-lock-comment-face))) 288 | 289 | (defconst purescript-font-lock-keywords 290 | (purescript-font-lock-keywords-create nil) 291 | "Font lock definitions for non-literate PureScript.") 292 | 293 | (defconst purescript-font-lock-bird-literate-keywords 294 | (purescript-font-lock-keywords-create 'bird) 295 | "Font lock definitions for Bird-style literate PureScript.") 296 | 297 | (defconst purescript-font-lock-latex-literate-keywords 298 | (purescript-font-lock-keywords-create 'latex) 299 | "Font lock definitions for LaTeX-style literate PureScript.") 300 | 301 | ;;;###autoload 302 | (defun purescript-font-lock-choose-keywords () 303 | (cl-case purescript-literate 304 | (bird purescript-font-lock-bird-literate-keywords) 305 | ((latex tex) purescript-font-lock-latex-literate-keywords) 306 | (t purescript-font-lock-keywords))) 307 | 308 | (defun purescript-font-lock-choose-syntactic-keywords () 309 | (cl-case purescript-literate 310 | (bird purescript-bird-syntactic-keywords) 311 | ((latex tex) purescript-latex-syntactic-keywords) 312 | (t purescript-basic-syntactic-keywords))) 313 | 314 | (defun purescript-font-lock-defaults-create () 315 | "Locally set `font-lock-defaults' for PureScript." 316 | (set (make-local-variable 'font-lock-defaults) 317 | '(purescript-font-lock-choose-keywords 318 | nil nil ((?\' . "w") (?_ . "w")) nil 319 | (font-lock-syntactic-keywords 320 | . purescript-font-lock-choose-syntactic-keywords) 321 | (font-lock-syntactic-face-function 322 | . purescript-syntactic-face-function) 323 | ;; Get help from font-lock-syntactic-keywords. 324 | (parse-sexp-lookup-properties . t)))) 325 | 326 | (provide 'purescript-font-lock) 327 | 328 | ;; Local Variables: 329 | ;; tab-width: 8 330 | ;; End: 331 | 332 | ;;; purescript-font-lock.el ends here 333 | -------------------------------------------------------------------------------- /tests/purescript-font-lock-tests.el: -------------------------------------------------------------------------------- 1 | ;;; purescript-font-lock-tests.el --- Unit tests for purescript font-lock -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (c) 2025 Konstantin Kharlamov. All rights reserved. 4 | 5 | ;; This file is free software; you can redistribute it and/or modify 6 | ;; it under the terms of the GNU General Public License as published by 7 | ;; the Free Software Foundation; either version 3, or (at your option) 8 | ;; any later version. 9 | 10 | ;; This file is distributed in the hope that it will be useful, 11 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | ;; GNU General Public License for more details. 14 | 15 | ;; You should have received a copy of the GNU General Public License 16 | ;; along with this program. If not, see . 17 | 18 | ;;; Code: 19 | 20 | (require 'ert) 21 | (require 'purescript-mode) 22 | 23 | (defun purescript-test-ranges (text ranges-list) 24 | (with-temp-buffer 25 | (insert text) 26 | (purescript-mode) 27 | (font-lock-ensure) 28 | (let ((ret 29 | (catch 'fail 30 | (dolist (range ranges-list) 31 | (let ((begin (nth 0 range)) 32 | (end (nth 1 range)) 33 | (face-expected (nth 2 range))) 34 | (dolist (pos (number-sequence begin end)) 35 | (let ((face-found (get-char-property pos 'face))) 36 | (when (not (eq face-found face-expected)) 37 | (throw 'fail `(,begin ,end ,face-expected ,face-found ,pos))))))) 38 | nil))) 39 | (when ret 40 | (message "Range [%d:%d] has face %s (expected %s) at %d" 41 | (nth 0 ret) (nth 1 ret) (nth 3 ret) (nth 2 ret) (nth 4 ret)) 42 | (should-not ret))))) 43 | 44 | (ert-deftest imports () 45 | (purescript-test-ranges 46 | "import Data.Array (many) 47 | import Data.Array as Array 48 | import Data.Either (Either(..)) 49 | " '((1 6 font-lock-keyword-face) 50 | (8 17 font-lock-type-face) 51 | (26 31 font-lock-keyword-face) 52 | (33 42 font-lock-type-face) 53 | (44 45 font-lock-keyword-face) 54 | (47 51 font-lock-type-face) 55 | (53 58 font-lock-keyword-face) 56 | (60 70 font-lock-type-face) 57 | (73 78 font-lock-type-face) 58 | (80 81 font-lock-builtin-face)))) 59 | 60 | (ert-deftest string () 61 | (purescript-test-ranges 62 | "foo = \"hello\"" 63 | '((1 3 font-lock-function-name-face) 64 | (5 5 font-lock-builtin-face) 65 | (7 13 font-lock-string-face)))) 66 | 67 | (ert-deftest multiline-string () 68 | (purescript-test-ranges 69 | "foo = \"\"\" 70 | hello 71 | \"\"\" 72 | " 73 | '((1 3 font-lock-function-name-face) 74 | (5 5 font-lock-builtin-face) 75 | (7 19 font-lock-string-face)))) 76 | 77 | (ert-deftest multiline-string-with-hash () 78 | (purescript-test-ranges 79 | "foo = \"\"\" 80 | # a string with hashtag 81 | # another # one 82 | -- not a comment -- 83 | -- | not a comment 84 | {- not a comment -} 85 | \"\"\" 86 | " 87 | '((1 3 font-lock-function-name-face) 88 | (5 5 font-lock-builtin-face) 89 | (7 114 font-lock-string-face)))) 90 | 91 | (ert-deftest multiline-string-with-embedded-strings () 92 | :expected-result :failed 93 | (purescript-test-ranges 94 | "foo = \"\"\" 95 | this = \"still a string\" 96 | \"\"\" 97 | " 98 | '((1 3 font-lock-function-name-face) 99 | (5 5 font-lock-builtin-face) 100 | (7 37 font-lock-string-face)))) 101 | 102 | (ert-deftest docs-bar-comment-different-spacings () 103 | (purescript-test-ranges 104 | "--| Docs comment 0 space 105 | -- | Docs comment 1 space 106 | -- | Docs comment many spaces 107 | " 108 | '((1 85 font-lock-doc-face)))) 109 | 110 | (ert-deftest docs-bar-comment-continuation () 111 | "Acc. to 112 | https://github.com/purescript/documentation/blob/master/language/Syntax.md 113 | PureScript explicitly doesn't support Haskell-style docs continuation 114 | where vertical bar is omitted" 115 | (purescript-test-ranges 116 | "-- | Docs start 117 | -- continue 118 | " 119 | '((1 16 font-lock-doc-face) 120 | (17 19 font-lock-comment-delimiter-face) 121 | (20 28 font-lock-comment-face)))) 122 | 123 | (ert-deftest docs-cap-comment-different-spacings () 124 | (purescript-test-ranges 125 | "-- ^ Docs comment space 126 | -- ^ Docs comment many spaces 127 | " 128 | '((1 57 font-lock-doc-face)))) 129 | 130 | ;; For some unknown reason this fails on older Emacses 131 | (when (>= emacs-major-version 28) 132 | (ert-deftest multiline-comment () 133 | (purescript-test-ranges 134 | "{- 135 | multiline comment 136 | -- | not a doc 137 | --| not a doc 138 | still comment 139 | -} 140 | noncomment 141 | {--} 142 | noncomment 143 | " 144 | '((1 64 font-lock-comment-face) 145 | (65 66 font-lock-comment-delimiter-face) 146 | (67 78 nil) 147 | (79 80 font-lock-comment-face) 148 | (81 82 font-lock-comment-delimiter-face) 149 | (83 93 nil))))) 150 | 151 | (ert-deftest multiline-comment-w-delimiter-inside () 152 | :expected-result :failed 153 | (purescript-test-ranges 154 | "{- {-{- -} noncomment" 155 | '((1 6 font-lock-comment-face) 156 | (7 10 font-lock-comment-delimiter-face) 157 | (11 21 nil)))) 158 | 159 | (ert-deftest type-with-typenames-and--> () 160 | (purescript-test-ranges 161 | "type Component props = Effect (props -> JSX)" 162 | '((1 4 font-lock-keyword-face) 163 | (5 5 nil) 164 | (6 14 font-lock-type-face) 165 | (15 21 nil) 166 | (22 22 font-lock-builtin-face) 167 | (23 23 nil) 168 | (24 29 font-lock-type-face) 169 | (30 37 nil) 170 | (38 39 font-lock-builtin-face) 171 | (40 40 nil) 172 | (41 43 font-lock-type-face) 173 | (44 45 nil)))) 174 | 175 | (ert-deftest module-in-different-locations () 176 | (purescript-test-ranges 177 | "module React.Basic.Hooks ( Component, module React.Basic 178 | , module Data.Tuple.Nested ) where 179 | " 180 | '((1 6 font-lock-keyword-face) 181 | (7 7 nil) 182 | (8 24 font-lock-type-face) 183 | (25 27 nil) 184 | (28 36 font-lock-type-face) 185 | (37 38 nil) 186 | (39 44 font-lock-keyword-face) 187 | (45 45 nil) 188 | (46 56 font-lock-type-face) 189 | (57 84 nil) 190 | (85 90 font-lock-keyword-face) 191 | (91 91 nil) 192 | (92 108 font-lock-type-face) 193 | (109 111 nil) 194 | (112 116 font-lock-keyword-face) 195 | (117 117 nil)))) 196 | 197 | (ert-deftest func-decl-w-do-and-qualified-do () 198 | (purescript-test-ranges 199 | "mkMyComponent :: Component {} 200 | mkMyComponent = do 201 | modalComp :: (NodeRef -> JSX) <- mkModal 202 | component \"mkMyComponent\" \\_ -> React.do 203 | dialogRef :: NodeRef <- newNodeRef 204 | pure $ R.label_ [] 205 | " 206 | '((1 13 font-lock-function-name-face) 207 | (14 14 nil) 208 | (15 16 font-lock-builtin-face) 209 | (17 17 nil) 210 | (18 26 font-lock-type-face) 211 | (27 30 nil) 212 | (31 43 font-lock-function-name-face) 213 | (44 44 nil) 214 | (45 45 font-lock-builtin-face) 215 | (46 46 nil) 216 | (47 48 font-lock-keyword-face) 217 | (49 61 nil) 218 | (62 63 font-lock-builtin-face) 219 | (64 65 nil) 220 | (66 72 font-lock-type-face) 221 | (73 73 nil) 222 | (74 75 font-lock-builtin-face) 223 | (76 76 nil) 224 | (77 79 font-lock-type-face) 225 | (80 81 nil) 226 | (82 83 font-lock-builtin-face) 227 | (84 104 nil) 228 | (105 119 font-lock-string-face) 229 | (120 120 nil) 230 | (121 121 font-lock-builtin-face) 231 | (122 122 font-lock-keyword-face) 232 | (123 123 nil) 233 | (124 125 font-lock-builtin-face) 234 | (126 126 nil) 235 | (127 131 font-lock-type-face) 236 | (132 132 font-lock-builtin-face) 237 | (133 134 font-lock-keyword-face) 238 | (135 149 nil) 239 | (150 151 font-lock-builtin-face) 240 | (152 152 nil) 241 | (153 159 font-lock-type-face) 242 | (160 160 nil) 243 | (161 162 font-lock-builtin-face) 244 | (163 181 nil) 245 | (182 182 font-lock-builtin-face) 246 | (183 183 nil) 247 | (184 184 font-lock-type-face) 248 | (185 185 font-lock-builtin-face) 249 | (186 192 nil) 250 | (193 194 font-lock-type-face) 251 | (195 195 nil)))) 252 | 253 | (ert-deftest instance-miscellaneous () 254 | "A diverse code snippet using `instance' (from Data.List module)" 255 | (purescript-test-ranges 256 | "instance extendNonEmptyList :: Extend NonEmptyList where 257 | extend f w@(NonEmptyList (_ :| as)) = 258 | NonEmptyList (f w :| (foldr go { val: Nil, acc: Nil } as).val) 259 | where 260 | go a { val, acc } = { val: f (NonEmptyList (a :| acc)) : val, acc: a : acc } 261 | instance semigroupNonEmptyList :: Semigroup (NonEmptyList a) where 262 | append (NonEmptyList (a :| as)) as' = 263 | NonEmptyList (a :| as <> toList as') 264 | derive newtype instance foldableNonEmptyList :: Foldable NonEmptyList 265 | " 266 | '((1 8 font-lock-keyword-face) (9 28 nil) 267 | (29 30 font-lock-builtin-face) (31 31 nil) 268 | (32 37 font-lock-type-face) (38 38 nil) 269 | (39 50 font-lock-type-face) (51 51 nil) 270 | (52 56 font-lock-keyword-face) (57 69 nil) 271 | (70 70 font-lock-builtin-face) (71 71 nil) 272 | (72 83 font-lock-type-face) (84 85 nil) 273 | (86 86 font-lock-keyword-face) (87 87 nil) 274 | (88 89 font-lock-builtin-face) (90 95 nil) 275 | (96 96 font-lock-builtin-face) (97 101 nil) 276 | (102 113 font-lock-type-face) (114 119 nil) 277 | (120 121 font-lock-builtin-face) (122 137 nil) 278 | (138 138 font-lock-builtin-face) (139 139 nil) 279 | (140 142 font-lock-type-face) (143 147 nil) 280 | (148 148 font-lock-builtin-face) (149 149 nil) 281 | (150 152 font-lock-type-face) (153 158 nil) 282 | (159 159 font-lock-builtin-face) (160 168 nil) 283 | (169 173 font-lock-keyword-face) (174 196 nil) 284 | (197 197 font-lock-builtin-face) (198 203 nil) 285 | (204 204 font-lock-builtin-face) (205 208 nil) 286 | (209 220 font-lock-type-face) (221 224 nil) 287 | (225 226 font-lock-builtin-face) (227 233 nil) 288 | (234 234 font-lock-builtin-face) (235 243 nil) 289 | (244 244 font-lock-builtin-face) (245 247 nil) 290 | (248 248 font-lock-builtin-face) (249 255 nil) 291 | (256 263 font-lock-keyword-face) (264 286 nil) 292 | (287 288 font-lock-builtin-face) (289 289 nil) 293 | (290 298 font-lock-type-face) (299 300 nil) 294 | (301 312 font-lock-type-face) (313 316 nil) 295 | (317 321 font-lock-keyword-face) (322 332 nil) 296 | (333 344 font-lock-type-face) (345 348 nil) 297 | (349 350 font-lock-builtin-face) (351 360 nil) 298 | (361 361 font-lock-builtin-face) (362 366 nil) 299 | (367 378 font-lock-type-face) (379 382 nil) 300 | (383 384 font-lock-builtin-face) (385 388 nil) 301 | (389 390 font-lock-builtin-face) (391 403 nil) 302 | (404 409 font-lock-keyword-face) (410 448 nil) 303 | (449 450 font-lock-builtin-face) (451 451 nil) 304 | (452 459 font-lock-type-face) (460 460 nil) 305 | (461 472 font-lock-type-face) (473 473 nil)))) 306 | 307 | (ert-deftest foreign-imports () 308 | (purescript-test-ranges 309 | "foreign import func2 :: Effect Int 310 | foreign import func3 311 | :: Effect Int 312 | foreign import 313 | func4 :: Effect Int 314 | foreign import func5 -- invalid indentation, but allowed in other context 315 | invalid_dont_highlight foreign import func6 316 | foreign importinvalid 317 | " 318 | '((1 7 font-lock-keyword-face) 319 | (8 8 nil) 320 | (9 14 font-lock-keyword-face) 321 | (15 21 nil) 322 | (22 23 font-lock-builtin-face) 323 | (24 24 nil) 324 | (25 30 font-lock-type-face) 325 | (31 31 nil) 326 | (32 34 font-lock-type-face) 327 | (35 35 nil) 328 | (36 42 font-lock-keyword-face) 329 | (43 43 nil) 330 | (44 49 font-lock-keyword-face) 331 | (50 58 nil) 332 | (59 60 font-lock-builtin-face) 333 | (61 61 nil) 334 | (62 67 font-lock-type-face) 335 | (68 68 nil) 336 | (69 71 font-lock-type-face) 337 | (72 72 nil) 338 | (73 79 font-lock-keyword-face) 339 | (80 80 nil) 340 | (81 86 font-lock-keyword-face) 341 | (87 95 nil) 342 | (96 97 font-lock-builtin-face) 343 | (98 98 nil) 344 | (99 104 font-lock-type-face) 345 | (105 105 nil) 346 | (106 108 font-lock-type-face) 347 | (109 111 nil) 348 | (112 118 font-lock-keyword-face) 349 | (119 119 nil) 350 | (120 125 font-lock-keyword-face) 351 | (126 132 nil) 352 | (133 135 font-lock-comment-delimiter-face) 353 | (136 185 font-lock-comment-face) 354 | (186 207 font-lock-function-name-face) 355 | (208 229 nil) 356 | (230 236 font-lock-function-name-face) 357 | (237 251 nil)))) 358 | 359 | (ert-deftest operator-faces-simple () 360 | "Tests operator faces unrelated to backticks" 361 | (purescript-test-ranges 362 | "-- .. 363 | import Data.Maybe (Maybe(..)) 364 | 365 | -- -> and :: 366 | id :: a -> a 367 | id = \\x -> x 368 | 369 | -- <- 370 | main = do 371 | n <- pure 42 372 | pure n 373 | 374 | -- => and = 375 | showVal :: Show a => a -> String 376 | showVal = show 377 | 378 | -- @ 379 | firstElem j@(Just _) = j 380 | 381 | -- guard | 382 | abs' x | x < 0 = -x 383 | | otherwise = x 384 | 385 | -- ~ and unicode ∷ 386 | toUnfoldable ∷ forall f. Unfoldable f => List ~> f 387 | toUnfoldable = undefined 388 | 389 | -- : as an operator 390 | arr :: Array Int 391 | arr = 1 : [2,3] 392 | " 393 | '((1 3 font-lock-comment-delimiter-face) 394 | (4 6 font-lock-comment-face) 395 | (7 12 font-lock-keyword-face) 396 | (13 13 nil) 397 | (14 23 font-lock-type-face) 398 | (24 25 nil) 399 | (26 30 font-lock-type-face) 400 | (31 31 nil) 401 | (32 33 font-lock-builtin-face) 402 | (34 37 nil) 403 | (38 40 font-lock-comment-delimiter-face) 404 | (41 50 font-lock-comment-face) 405 | (51 52 font-lock-function-name-face) 406 | (53 53 nil) 407 | (54 55 font-lock-builtin-face) 408 | (56 58 nil) 409 | (59 60 font-lock-builtin-face) 410 | (61 63 nil) 411 | (64 65 font-lock-function-name-face) 412 | (66 66 nil) 413 | (67 67 font-lock-builtin-face) 414 | (68 68 nil) 415 | (69 69 font-lock-builtin-face) 416 | (70 71 nil) 417 | (72 73 font-lock-builtin-face) 418 | (74 77 nil) 419 | (78 80 font-lock-comment-delimiter-face) 420 | (81 83 font-lock-comment-face) 421 | (84 87 font-lock-function-name-face) 422 | (88 88 nil) 423 | (89 89 font-lock-builtin-face) 424 | (90 90 nil) 425 | (91 92 font-lock-keyword-face) 426 | (93 97 nil) 427 | (98 99 font-lock-builtin-face) 428 | (100 118 nil) 429 | (119 121 font-lock-comment-delimiter-face) 430 | (122 130 font-lock-comment-face) 431 | (131 137 font-lock-function-name-face) 432 | (138 138 nil) 433 | (139 140 font-lock-builtin-face) 434 | (141 141 nil) 435 | (142 145 font-lock-type-face) 436 | (146 148 nil) 437 | (149 150 font-lock-builtin-face) 438 | (151 153 nil) 439 | (154 155 font-lock-builtin-face) 440 | (156 156 nil) 441 | (157 162 font-lock-type-face) 442 | (163 163 nil) 443 | (164 170 font-lock-function-name-face) 444 | (171 171 nil) 445 | (172 172 font-lock-builtin-face) 446 | (173 179 nil) 447 | (180 182 font-lock-comment-delimiter-face) 448 | (183 184 font-lock-comment-face) 449 | (185 193 font-lock-function-name-face) 450 | (194 195 nil) 451 | (196 196 font-lock-builtin-face) 452 | (197 197 nil) 453 | (198 201 font-lock-type-face) 454 | (202 202 nil) 455 | (203 203 font-lock-keyword-face) 456 | (204 205 nil) 457 | (206 206 font-lock-builtin-face) 458 | (207 210 nil) 459 | (211 213 font-lock-comment-delimiter-face) 460 | (214 221 font-lock-comment-face) 461 | (222 225 font-lock-function-name-face) 462 | (226 228 nil) 463 | (229 229 font-lock-builtin-face) 464 | (230 232 nil) 465 | (233 233 font-lock-builtin-face) 466 | (234 236 nil) 467 | (237 237 font-lock-builtin-face) 468 | (238 238 nil) 469 | (239 239 font-lock-builtin-face) 470 | (240 248 nil) 471 | (249 249 font-lock-builtin-face) 472 | (250 260 nil) 473 | (261 261 font-lock-builtin-face) 474 | (262 265 nil) 475 | (266 268 font-lock-comment-delimiter-face) 476 | (269 284 font-lock-comment-face) 477 | (285 296 font-lock-function-name-face) 478 | (297 297 nil) 479 | (298 298 font-lock-builtin-face) 480 | (299 307 nil) 481 | (308 308 font-lock-builtin-face) 482 | (309 309 nil) 483 | (310 319 font-lock-type-face) 484 | (320 322 nil) 485 | (323 324 font-lock-builtin-face) 486 | (325 325 nil) 487 | (326 329 font-lock-type-face) 488 | (330 330 nil) 489 | (331 332 font-lock-builtin-face) 490 | (333 335 nil) 491 | (336 347 font-lock-function-name-face) 492 | (348 348 nil) 493 | (349 349 font-lock-builtin-face) 494 | (350 361 nil) 495 | (362 364 font-lock-comment-delimiter-face) 496 | (365 381 font-lock-comment-face) 497 | (382 384 font-lock-function-name-face) 498 | (385 385 nil) 499 | (386 387 font-lock-builtin-face) 500 | (388 388 nil) 501 | (389 393 font-lock-type-face) 502 | (394 394 nil) 503 | (395 397 font-lock-type-face) 504 | (398 398 nil) 505 | (399 401 font-lock-function-name-face) 506 | (402 402 nil) 507 | (403 403 font-lock-builtin-face) 508 | (404 406 nil) 509 | (407 407 font-lock-builtin-face) 510 | (408 414 nil)))) 511 | 512 | (ert-deftest tuple-and-cap-highlighted () 513 | (purescript-test-ranges 514 | "myTuple = 1 /\\ (2 ^ 3)" 515 | '((1 7 font-lock-function-name-face) 516 | (8 8 nil) 517 | (9 9 font-lock-builtin-face) 518 | (10 12 nil) 519 | (13 14 font-lock-builtin-face) 520 | (15 18 nil) 521 | (19 19 font-lock-builtin-face) 522 | (20 23 nil)))) 523 | 524 | (ert-deftest backtick-operator () 525 | (purescript-test-ranges 526 | "var = 1 `func` 2" 527 | '((1 3 font-lock-function-name-face) 528 | (4 4 nil) 529 | (5 5 font-lock-builtin-face) 530 | (6 8 nil) 531 | (9 14 font-lock-builtin-face) 532 | (15 17 nil)))) 533 | -------------------------------------------------------------------------------- /purescript-mode.el: -------------------------------------------------------------------------------- 1 | ;;; purescript-mode.el --- A PureScript editing mode -*- coding: utf-8 lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc 4 | ;; Copyright (C) 1992, 1997-1998 Simon Marlow, Graeme E Moss, and Tommy Thorn 5 | 6 | ;; Author: 1992 Simon Marlow 7 | ;; 1997-1998 Graeme E Moss and 8 | ;; Tommy Thorn , 9 | ;; 2001-2002 Reuben Thomas (>=v1.4) 10 | ;; 2003 Dave Love 11 | ;; 2014 Tim Dysinger 12 | ;; Keywords: faces files PureScript 13 | ;; URL: https://github.com/purescript-emacs/purescript-mode 14 | ;; Package-Version: @VERSION@ 15 | ;; Package-Requires: ((emacs "25.1")) 16 | 17 | ;; This file is not part of GNU Emacs. 18 | 19 | ;; This file is free software; you can redistribute it and/or modify 20 | ;; it under the terms of the GNU General Public License as published by 21 | ;; the Free Software Foundation; either version 3, or (at your option) 22 | ;; any later version. 23 | 24 | ;; This file is distributed in the hope that it will be useful, 25 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 26 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 27 | ;; GNU General Public License for more details. 28 | 29 | ;; You should have received a copy of the GNU General Public License 30 | ;; along with this program. If not, see . 31 | 32 | ;;; Code: 33 | 34 | (require 'dabbrev) 35 | (require 'compile) 36 | (require 'outline) 37 | (require 'purescript-vars) 38 | (require 'purescript-align-imports) 39 | (require 'purescript-sort-imports) 40 | (require 'purescript-font-lock) 41 | (require 'cl-lib) 42 | (cl-eval-when 'compile (require 'find-file)) 43 | 44 | ;; All functions/variables start with `(literate-)purescript-'. 45 | 46 | ;; Version of mode. 47 | (defconst purescript-version "@VERSION@" 48 | "The release version of `purescript-mode'.") 49 | 50 | (defconst purescript-git-version "@GIT_VERSION@" 51 | "The Git version of `purescript-mode'.") 52 | 53 | ;;;###autoload 54 | (defun purescript-version (&optional here) 55 | "Show the `purescript-mode` version in the echo area. 56 | With prefix argument HERE, insert it at point. 57 | When FULL is non-nil, use a verbose version string. 58 | When MESSAGE is non-nil, display a message with the version." 59 | (interactive "P") 60 | (let* ((purescript-mode-dir (ignore-errors 61 | (file-name-directory (or (locate-library "purescript-mode") "")))) 62 | (version (format "purescript-mode version %s (%s @ %s)" 63 | purescript-version 64 | purescript-git-version 65 | purescript-mode-dir))) 66 | (if here 67 | (insert version) 68 | (message "%s" version)))) 69 | 70 | (defgroup purescript nil 71 | "Major mode for editing PureScript programs." 72 | :link '(custom-manual "(purescript-mode)") 73 | :group 'languages 74 | :prefix "purescript-") 75 | 76 | ;;;###autoload 77 | (defun purescript-customize () 78 | "Browse the purescript customize sub-tree. 79 | This calls `customize-browse' with purescript as argument and makes 80 | sure all purescript customize definitions have been loaded." 81 | (interactive) 82 | ;; make sure all modules with (defcustom ...)s are loaded 83 | (mapc 'require 84 | '(purescript-indentation 85 | purescript-indent 86 | purescript-interactive-mode 87 | purescript-yas)) 88 | (customize-browse 'purescript)) 89 | 90 | ;; Default literate style for ambiguous literate buffers. 91 | (defcustom purescript-literate-default 'bird 92 | "Default value for `purescript-literate'. 93 | Used if the style of a literate buffer is ambiguous. This variable should 94 | be set to the preferred literate style." 95 | :group 'purescript 96 | :type '(choice (const bird) (const tex) (const nil))) 97 | ;;;###autoload 98 | (defvar purescript-mode-map 99 | (let ((map (make-sparse-keymap))) 100 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 101 | ;; Editing-specific commands 102 | (define-key map (kbd "C-c C-.") 'purescript-mode-format-imports) 103 | (define-key map [remap delete-indentation] 'purescript-delete-indentation) 104 | 105 | map) 106 | "Keymap used in PureScript mode.") 107 | 108 | ;; Syntax table. 109 | (defvar purescript-mode-syntax-table 110 | (let ((table (make-syntax-table))) 111 | (modify-syntax-entry ?\ " " table) 112 | (modify-syntax-entry ?\t " " table) 113 | (modify-syntax-entry ?\" "\"" table) 114 | (modify-syntax-entry ?\' "\'" table) 115 | (modify-syntax-entry ?_ "w" table) 116 | (modify-syntax-entry ?\( "()" table) 117 | (modify-syntax-entry ?\) ")(" table) 118 | (modify-syntax-entry ?\[ "(]" table) 119 | (modify-syntax-entry ?\] ")[" table) 120 | 121 | (cond ((featurep 'xemacs) 122 | ;; I don't know whether this is equivalent to the below 123 | ;; (modulo nesting). -- fx 124 | (modify-syntax-entry ?{ "(}5" table) 125 | (modify-syntax-entry ?} "){8" table) 126 | (modify-syntax-entry ?- "_ 1267" table)) 127 | (t 128 | ;; In Emacs 21, the `n' indicates that they nest. 129 | ;; The `b' annotation is actually ignored because it's only 130 | ;; meaningful on the second char of a comment-starter, so 131 | ;; on Emacs 20 and before we get wrong results. --Stef 132 | (modify-syntax-entry ?\{ "(}1nb" table) 133 | (modify-syntax-entry ?\} "){4nb" table) 134 | (modify-syntax-entry ?- "_ 123" table))) 135 | (modify-syntax-entry ?\n ">" table) 136 | 137 | (let (i lim) 138 | (map-char-table 139 | (lambda (k v) 140 | (when (equal v '(1)) 141 | ;; The current Emacs 22 codebase can pass either a char 142 | ;; or a char range. 143 | (if (consp k) 144 | (setq i (car k) 145 | lim (cdr k)) 146 | (setq i k 147 | lim k)) 148 | (while (<= i lim) 149 | (when (> i 127) 150 | (modify-syntax-entry i "_" table)) 151 | (setq i (1+ i))))) 152 | (standard-syntax-table))) 153 | 154 | (modify-syntax-entry ?\` "$`" table) 155 | (modify-syntax-entry ?\\ "\\" table) 156 | (mapc (lambda (x) 157 | (modify-syntax-entry x "_" table)) 158 | ;; Some of these are actually OK by default. 159 | "!#$%&*+./:<=>?@^|~") 160 | (unless (featurep 'mule) 161 | ;; Non-ASCII syntax should be OK, at least in Emacs. 162 | (mapc (lambda (x) 163 | (modify-syntax-entry x "_" table)) 164 | (concat "¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿" 165 | "×÷")) 166 | (mapc (lambda (x) 167 | (modify-syntax-entry x "w" table)) 168 | (concat "ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ" 169 | "ØÙÚÛÜÝÞß" 170 | "àáâãäåæçèéêëìíîïðñòóôõö" 171 | "øùúûüýþÿ"))) 172 | table) 173 | "Syntax table used in PureScript mode.") 174 | 175 | (defun purescript-ident-at-point () 176 | "Return the identifier under point, or nil if none found. 177 | May return a qualified name." 178 | (let ((reg (purescript-ident-pos-at-point))) 179 | (when reg 180 | (buffer-substring-no-properties (car reg) (cdr reg))))) 181 | 182 | (defun purescript-ident-pos-at-point () 183 | "Return the span of the identifier under point, or nil if none found. 184 | May return a qualified name." 185 | (save-excursion 186 | ;; Skip whitespace if we're on it. That way, if we're at "map ", we'll 187 | ;; see the word "map". 188 | (if (and (not (eobp)) 189 | (eq ? (char-syntax (char-after)))) 190 | (skip-chars-backward " \t")) 191 | 192 | (let ((case-fold-search nil)) 193 | (cl-multiple-value-bind (start end) 194 | (if (looking-at "\\s_") 195 | (list (progn (skip-syntax-backward "_") (point)) 196 | (progn (skip-syntax-forward "_") (point))) 197 | (list 198 | (progn (skip-syntax-backward "w'") 199 | (skip-syntax-forward "'") (point)) 200 | (progn (skip-syntax-forward "w'") (point)))) 201 | ;; If we're looking at a module ID that qualifies further IDs, add 202 | ;; those IDs. 203 | (goto-char start) 204 | (while (and (looking-at "[[:upper:]]") (eq (char-after end) ?.) 205 | ;; It's a module ID that qualifies further IDs. 206 | (goto-char (1+ end)) 207 | (save-excursion 208 | (when (not (zerop (skip-syntax-forward 209 | (if (looking-at "\\s_") "_" "w'")))) 210 | (setq end (point)))))) 211 | ;; If we're looking at an ID that's itself qualified by previous 212 | ;; module IDs, add those too. 213 | (goto-char start) 214 | (if (eq (char-after) ?.) (forward-char 1)) ;Special case for "." 215 | (while (and (eq (char-before) ?.) 216 | (progn (forward-char -1) 217 | (not (zerop (skip-syntax-backward "w'")))) 218 | (skip-syntax-forward "'") 219 | (looking-at "[[:upper:]]")) 220 | (setq start (point))) 221 | ;; This is it. 222 | (cons start end))))) 223 | 224 | (defun purescript-delete-indentation (&optional arg) 225 | "Like `delete-indentation' but ignoring Bird-style \">\"." 226 | (interactive "*P") 227 | (let ((fill-prefix (or fill-prefix (if (eq purescript-literate 'bird) ">")))) 228 | (delete-indentation arg))) 229 | 230 | ;; Various mode variables. 231 | 232 | (defcustom purescript-mode-hook nil 233 | "Hook run after entering `purescript-mode'. 234 | 235 | Some of the supported modules that can be activated via this hook: 236 | 237 | `purescript-indentation', Kristof Bastiaensen 238 | Intelligent semi-automatic indentation Mk2 239 | 240 | `purescript-indent', Guy Lapalme 241 | Intelligent semi-automatic indentation. 242 | 243 | `purescript-simple-indent', Graeme E Moss and Heribert Schuetz 244 | Simple indentation. 245 | 246 | Module X is activated using the command `turn-on-X'. For example, 247 | `purescript-indent' is activated using `turn-on-purescript-indent'. 248 | For more information on a specific module, see the help for its `X-mode' 249 | function. Some modules can be deactivated using `turn-off-X'. 250 | 251 | See Info node `(purescript-mode)purescript-mode-hook' for more details. 252 | 253 | Warning: do not enable more than one of the three indentation 254 | modes. See Info node `(purescript-mode)indentation' for more 255 | details." 256 | :type 'hook 257 | :group 'purescript 258 | :link '(info-link "(purescript-mode)purescript-mode-hook") 259 | :link '(function-link purescript-mode) 260 | :options `(capitalized-words-mode 261 | turn-on-eldoc-mode 262 | turn-on-purescript-indent 263 | turn-on-purescript-indentation 264 | turn-on-purescript-simple-indent 265 | turn-on-purescript-unicode-input-method)) 266 | 267 | ;; The main mode functions 268 | ;;;###autoload 269 | (define-derived-mode purescript-mode prog-mode "PureScript" 270 | "Major mode for editing PureScript programs. 271 | 272 | See also Info node `(purescript-mode)Getting Started' for more 273 | information about this mode. 274 | 275 | \\ 276 | Literate scripts are supported via `literate-purescript-mode'. 277 | The variable `purescript-literate' indicates the style of the script in the 278 | current buffer. See the documentation on this variable for more details. 279 | 280 | Use `purescript-version' to find out what version of PureScript mode you are 281 | currently using. 282 | 283 | Additional PureScript mode modules can be hooked in via `purescript-mode-hook'; 284 | see documentation for that variable for more details." 285 | :group 'purescript 286 | (set (make-local-variable 'paragraph-start) (concat "^$\\|" page-delimiter)) 287 | (set (make-local-variable 'paragraph-separate) paragraph-start) 288 | (set (make-local-variable 'fill-paragraph-function) 'purescript-fill-paragraph) 289 | ;; (set (make-local-variable 'adaptive-fill-function) 'purescript-adaptive-fill) 290 | (set (make-local-variable 'adaptive-fill-mode) nil) 291 | (set (make-local-variable 'comment-start) "-- ") 292 | (set (make-local-variable 'comment-padding) 0) 293 | (set (make-local-variable 'comment-start-skip) "--\s*|?\s*") 294 | (set (make-local-variable 'comment-end) "") 295 | (set (make-local-variable 'comment-end-skip) "[ \t]*\\(-}\\|\\s>\\)") 296 | (set (make-local-variable 'parse-sexp-ignore-comments) nil) 297 | (set (make-local-variable 'indent-line-function) 'purescript-mode-suggest-indent-choice) 298 | (purescript-font-lock-defaults-create) ; set things up for font-lock. 299 | ;; PureScript's layout rules mean that TABs have to be handled with extra care. 300 | ;; The safer option is to avoid TABs. The second best is to make sure 301 | ;; TABs stops are 8 chars apart, as mandated by the PureScript Report. --Stef 302 | (set (make-local-variable 'indent-tabs-mode) nil) 303 | (set (make-local-variable 'tab-width) 8) 304 | ;; dynamic abbrev support: recognize PureScript identifiers 305 | ;; PureScript is case-sensitive language 306 | (set (make-local-variable 'dabbrev-case-fold-search) nil) 307 | (set (make-local-variable 'dabbrev-case-distinction) nil) 308 | (set (make-local-variable 'dabbrev-case-replace) nil) 309 | (set (make-local-variable 'dabbrev-abbrev-char-regexp) "\\sw\\|[.]") 310 | (setq-local beginning-of-defun-function 'purescript-beginning-of-defun) 311 | (setq prettify-symbols-alist purescript-font-lock-prettify-symbols-alist 312 | ;; make (ff-find-other-file) find .js FFI file, given .purs 313 | ff-other-file-alist '((".purs\\'" (".js"))))) 314 | 315 | (defun purescript-fill-paragraph (justify) 316 | (save-excursion 317 | ;; Fill paragraph should only work in comments. 318 | ;; The -- comments are handled properly by default 319 | ;; The {- -} comments need some extra love. 320 | (let* ((syntax-values (syntax-ppss)) 321 | (comment-num (nth 4 syntax-values))) 322 | (cond 323 | ((eq t comment-num) 324 | ;; standard fill works wonders inside a non-nested comment 325 | (fill-comment-paragraph justify)) 326 | 327 | ((integerp comment-num) 328 | ;; we are in a nested comment. lets narrow to comment content 329 | ;; and use plain paragraph fill for that 330 | (let* ((comment-start-point (nth 8 syntax-values)) 331 | (comment-end-point 332 | (save-excursion 333 | (re-search-forward "-}" (point-max) t comment-num) 334 | (point))) 335 | (fill-paragraph-handle-comment nil)) 336 | (save-restriction 337 | (narrow-to-region (+ 2 comment-start-point) (- comment-end-point 2)) 338 | (fill-paragraph justify)))) 339 | ((eolp) 340 | ;; do nothing outside of a comment 341 | t) 342 | (t 343 | ;; go to end of line and try again 344 | (end-of-line) 345 | (purescript-fill-paragraph justify)))))) 346 | 347 | 348 | ;; (defun purescript-adaptive-fill () 349 | ;; ;; We want to use "-- " as the prefix of "-- |", etc. 350 | ;; (let* ((line-end (save-excursion (end-of-line) (point))) 351 | ;; (line-start (point))) 352 | ;; (save-excursion 353 | ;; (unless (in-comment) 354 | ;; ;; Try to find the start of a comment. We only fill comments. 355 | ;; (search-forward-regexp comment-start-skip line-end t)) 356 | ;; (when (in-comment) 357 | ;; (let ();(prefix-start (point))) 358 | ;; (skip-syntax-forward "^w") 359 | ;; (make-string (- (point) line-start) ?\s)))))) 360 | 361 | 362 | 363 | ;;;###autoload 364 | (define-derived-mode literate-purescript-mode purescript-mode "LitPureScript" 365 | "As `purescript-mode' but for literate scripts." 366 | (setq purescript-literate 367 | (save-excursion 368 | (goto-char (point-min)) 369 | (cond 370 | ((re-search-forward "^\\\\\\(begin\\|end\\){code}$" nil t) 'tex) 371 | ((re-search-forward "^>" nil t) 'bird) 372 | (t purescript-literate-default)))) 373 | (if (eq purescript-literate 'bird) 374 | ;; fill-comment-paragraph isn't much use there, and even gets confused 375 | ;; by the syntax-table text-properties we add to mark the first char 376 | ;; of each line as a comment-starter. 377 | (set (make-local-variable 'fill-paragraph-handle-comment) nil)) 378 | (set (make-local-variable 'mode-line-process) 379 | '("/" (:eval (symbol-name purescript-literate))))) 380 | 381 | ;;;###autoload(add-to-list 'auto-mode-alist '("\\.purs\\'" . purescript-mode)) 382 | 383 | (defun purescript-pursuit (query &optional _info) 384 | "Do a Pursuit search for QUERY. 385 | When `purescript-pursuit-command' is non-nil, this command runs 386 | that. Otherwise, it opens a Pursuit search result in the browser. 387 | 388 | If prefix argument INFO is given, then `purescript-pursuit-command' 389 | is asked to show extra info for the items matching QUERY.." 390 | (interactive 391 | (let ((def (purescript-ident-at-point))) 392 | (if (and def (symbolp def)) (setq def (symbol-name def))) 393 | (list (read-string (if def 394 | (format "Pursuit query (default %s): " def) 395 | "Pursuit query: ") 396 | nil nil def) 397 | current-prefix-arg))) 398 | (browse-url (format "https://pursuit.purescript.org/search?q=%s" query))) 399 | 400 | (defcustom purescript-indent-spaces 2 401 | "Number of spaces to indent inwards." 402 | :type 'integer 403 | :safe 'integerp) 404 | 405 | (defun purescript-mode-suggest-indent-choice () 406 | "Ran when the user tries to indent in the buffer but no indentation mode 407 | has been selected. 408 | 409 | Brings up the documentation for purescript-mode-hook." 410 | (error 411 | "To use indentation you need to turn-on one of the indentation modes. Please see `purescript-mode-hook' documentation for examples. 412 | 413 | Currently `purescript-indentation' is the most maintained mode.")) 414 | 415 | (defun purescript-mode-format-imports () 416 | "Format the imports by aligning and sorting them." 417 | (interactive) 418 | (let ((col (current-column))) 419 | (purescript-sort-imports) 420 | (purescript-align-imports) 421 | (goto-char (+ (line-beginning-position) 422 | col)))) 423 | 424 | (defun purescript-string-take (string n) 425 | "Take n chars from string." 426 | (substring string 427 | 0 428 | (min (length string) n))) 429 | 430 | (defun purescript-mode-message-line (str) 431 | "Message only one line, multiple lines just disturbs the programmer." 432 | (let ((lines (split-string str "\n" t))) 433 | (when (and (car lines) (stringp (car lines))) 434 | (message "%s" 435 | (concat (car lines) 436 | (if (and (cdr lines) (stringp (cadr lines))) 437 | (format " [ %s .. ]" (purescript-string-take (string-trim (cadr lines)) 10)) 438 | "")))))) 439 | 440 | (defun purescript-current-line-string () 441 | "Returns current line as a string." 442 | (buffer-substring-no-properties (line-beginning-position) (line-end-position))) 443 | 444 | (defun purescript-beginning-of-defun-single () 445 | (while (and (looking-at-p (rx (* space) eol)) 446 | (not (bobp))) 447 | (forward-line -1)) ; can't get indentation on an empty line 448 | (let ((indent-level (current-indentation))) 449 | (while 450 | (not 451 | (or (ignore (forward-line -1)) ; do-while implementation 452 | (bobp) 453 | (and (< (current-indentation) indent-level) 454 | (string-match-p 455 | (rx (*? anything) 456 | (or bol space word-boundary) "=" 457 | (or eol space word-boundary)) 458 | ;; Emacs doesn't allow to limit search just to the curent line 459 | ;; barring the regex eol, but eol overly complicates matching. 460 | (purescript-current-line-string)))))))) 461 | 462 | (defun purescript-beginning-of-defun (&optional repeat) 463 | "Move point to the beginning of the current PureScript function." 464 | (purescript-beginning-of-defun-single) 465 | (dotimes (_ (if repeat 466 | (- repeat 1) ; the function was already called once 467 | 0)) 468 | (purescript-beginning-of-defun-single))) 469 | 470 | (provide 'purescript-mode) 471 | 472 | ;;; purescript-mode.el ends here 473 | -------------------------------------------------------------------------------- /purescript-mode.texi: -------------------------------------------------------------------------------- 1 | \input texinfo @c -*-texinfo-*- 2 | @c %**start of header 3 | @setfilename purescript-mode.info 4 | @documentencoding UTF-8 5 | @settitle PureScript Mode @@VERSION@@ 6 | @c %**end of header 7 | 8 | @dircategory Emacs 9 | @direntry 10 | * PureScript Mode: (purescript-mode). PureScript Development Environment for Emacs(en) 11 | @end direntry 12 | 13 | @copying 14 | This manual is for PureScript mode, version @@GIT_VERSION@@ 15 | 16 | Copyright @copyright{} 2013 PureScript Mode contributors. 17 | 18 | @quotation 19 | Permission is granted to copy, distribute and/or modify this document under the terms of the @uref{http://www.gnu.org/licenses/fdl.html,GNU Free Documentation License}, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts and no Back-Cover Texts. 20 | @end quotation 21 | @end copying 22 | 23 | @iftex 24 | @titlepage 25 | @title PureScript Mode 26 | @subtitle PureScript Development Environment for Emacs 27 | 28 | @page 29 | @vskip 0pt plus 1filll 30 | @insertcopying 31 | @end titlepage 32 | 33 | @contents 34 | @end iftex 35 | 36 | @ifhtml 37 | @titlepage 38 | @title PureScript Mode 39 | @subtitle PureScript Development Environment for Emacs 40 | 41 | @end titlepage 42 | @insertcopying 43 | @end ifhtml 44 | 45 | @ifnottex 46 | @node Top 47 | @top PureScript Mode 48 | 49 | PureScript Mode is an PureScript development Environment for GNU Emacs version 23 or later. It provides syntax-based indentation, font locking, editing cabal files, and supports running an inferior PureScript interpreter (e.g. GHCi). 50 | 51 | @end ifnottex 52 | 53 | @menu 54 | * Introduction:: 55 | * Getting Help and Reporting Bugs:: 56 | * Getting Started:: 57 | * Editing PureScript Code:: 58 | * Unicode:: 59 | * Indentation:: 60 | * Compilation:: 61 | * purescript-decl-scan-mode:: 62 | * inferior-purescript-mode:: 63 | * purescript-interactive-mode:: 64 | * purescript-cabal-mode:: 65 | * Concept index:: 66 | * Function index:: 67 | * Variable index:: 68 | 69 | @end menu 70 | 71 | @node Introduction 72 | @chapter Introduction 73 | 74 | @dfn{PureScript Mode} is a major mode providing a convenient environment for editing @uref{http://www.purescript.org,PureScript} programs. 75 | 76 | Some of its major features are: 77 | 78 | @itemize 79 | @item 80 | Syntax highlighting (font lock), 81 | @item 82 | automatic indentation, 83 | @item 84 | on-the-fly documentation, 85 | @item 86 | interaction with inferior GHCi/Hugs instance, and 87 | @item 88 | scanning declarations and placing them in a menu. 89 | @end itemize 90 | 91 | @node Getting Help and Reporting Bugs 92 | @chapter Getting Help and Reporting Bugs 93 | 94 | This Info manual is work in progress and incomplete. However, you can find more information at these locations in the meantime: 95 | 96 | @itemize 97 | @item 98 | @uref{https://github.com/purescript-emacs/purescript-mode,PureScript Mode's GitHub Home} 99 | @item 100 | @uref{http://www.purescript.org/purescriptwiki/PureScript_mode_for_Emacs,PureScript Wiki Emacs Page} 101 | @end itemize 102 | 103 | If you have any questions or like to discuss something regarding PureScript Mode, please consider sending an email to the @uref{http://projects.purescript.org/cgi-bin/mailman/listinfo/purescriptmode-emacs, PureScriptmode-emacs mailing list}. The mailing list is also available on @uref{http://gmane.org/, Gmane} via the @uref{http://dir.gmane.org/gmane.comp.lang.purescript.emacs, gmane.comp.lang.purescript.emacs} newsgroup. 104 | 105 | If you have discovered a bug or wish to request a new feature, you can @uref{https://github.com/purescript-emacs/purescript-mode/issues/new, file a new issue} with PureScript Mode's issue tracker. When filing a bug, please state your currently used software version (@kbd{M-x purescript-version}, @kbd{M-x version}) and what steps to perform in order to reproduce the bug you're experiencing. Finally, if you happen to be proficient in @ref{Top,Emacs Lisp,,elisp} you are welcome to submit patches via @uref{https://help.github.com/articles/using-pull-requests, GitHub Pull Requests}. 106 | 107 | @node Getting Started 108 | @chapter Getting Started 109 | 110 | If you are reading this, you have most likely already managed to install PureScript Mode in one way or another. However, just for the record, the officially recommended way is to install PureScript Mode via the @uref{http://marmalade-repo.org/packages/purescript-mode,Marmalade package archive} which contains the latest stable release of PureScript Mode. 111 | 112 | @findex purescript-customize 113 | Most of PureScript Mode's settings are configurable via customizable variables (@pxref{Easy Customization,,,emacs}, for details). You can use @kbd{M-x purescript-customize} to browse the @code{purescript} customization sub-tree. 114 | 115 | @vindex purescript-mode-hook 116 | One of the important setting you should customize is the @code{purescript-mode-hook} variable (@pxref{Hooks,,,emacs}) which gets run right after the @code{purescript-mode} major mode is initialized for a buffer. You can customize @code{purescript-mode-hook} by @kbd{M-x customize-variable @key{RET} purescript-mode-hook}. It's highly recommended you set up indentation to match your preferences; @xref{Indentation}, for more details about the indentation modes included with PureScript Mode. 117 | 118 | @c TODO: 119 | @c provide basic instructions to get up and running with purescript-mode 120 | @c tell about the most important commands 121 | 122 | @node Editing PureScript Code 123 | @chapter Editing PureScript Code 124 | 125 | @findex purescript-mode 126 | @cindex @code{purescript-mode} 127 | 128 | @dfn{PureScript Mode} is actually a collection of so-called major modes@footnote{for more information about the concept of @dfn{major modes} @pxref{Major Modes,,,emacs}} one of which is called @code{purescript-mode}. To avoid confusion, when referring to this package the name ``PureScript mode'' is written in a normal font, whereas when referring the major mode of the same name @code{purescript-mode} written with a dash in-between in a typewriter font is used. 129 | 130 | As one might guess, @code{purescript-mode} is the (programming language@footnote{@code{purescript-mode} is derived from @code{prog-mode}}) major mode for editing (non-literate) PureScript source code. @code{purescript-mode} is associated with the file extensions listed below by default@footnote{for more information about file associations, @pxref{Choosing Modes,,,emacs}}. 131 | 132 | @table @file 133 | @item .purs 134 | official file extension for (non-literate) PureScript 98/2010 files 135 | @item .pursc 136 | ``almost-PureScript'' input file for the @uref{http://www.purescript.org/ghc/docs/latest/html/users_guide/pursc2purs.html,pursc2purs} pre-processor 137 | @item .cpppurs 138 | input file for the @uref{http://projects.purescript.org/cpppurs/,cpppurs} pre-processor 139 | @end table 140 | 141 | @cindex literate programming 142 | @findex literate-purescript-mode 143 | 144 | @noindent 145 | The major mode @code{literate-purescript-mode} (which is derived from @code{purescript-mode} and thus transitively from @code{prog-mode}) provides support for @uref{http://www.purescript.org/purescriptwiki/Literate_programming,literate PureScript programs} and is associated with the @file{.lpurs} file extension by default. 146 | 147 | @code{literate-purescript-mode} supports Bird-style as well as @TeX{}-style literate PureScript files. The currently detected literate PureScript variant is shown in the mode line (@pxref{Mode Line,,,emacs}) as either @samp{LitPureScript/bird} or @samp{LitPureScript/tex}. 148 | 149 | @section Font Lock Support 150 | 151 | @code{purescript-mode} supports @dfn{syntax highlighting} via Emacs' Font Lock minor mode which should be enabled by default in current Emacsen. @xref{Font Lock,,,emacs}, for more information on how to control @code{font-lock-mode}. 152 | 153 | @node Unicode 154 | @chapter Unicode support 155 | 156 | @cindex Unicode 157 | 158 | See the PureScript Wiki's entry on @uref{http://www.purescript.org/purescriptwiki/Unicode-symbols, Unicode Symbols} for general information about Unicode support in PureScript. 159 | 160 | As Emacs supports editing files containing Unicode out of the box, so does PureScript Mode. As an add-on, PureScript Mode includes the @code{purescript-unicode} input method which allows you to easily type a number of Unicode symbols that are useful when writing PureScript code; @xref{Input Methods,,,emacs}, for more details. 161 | 162 | To automatically enable the @code{purescript-unicode} input method in purescript-mode buffers use @kbd{M-x customize-variable @key{RET} purescript-mode-hook} or put the following code in your @file{.emacs} file: 163 | 164 | @lisp 165 | (add-hook 'purescript-mode-hook 'turn-on-purescript-unicode-input-method) 166 | @end lisp 167 | 168 | @noindent 169 | To temporarily enable this input method for a single buffer you can use @kbd{M-x turn-on-purescript-unicode-input-method}. 170 | 171 | When the @code{purescript-unicode} input method is active, you can simply type @samp{->} and it is immediately replaced with @samp{→}. Use @kbd{C-\} to toggle the input method. To see a table of all key sequences use @kbd{M-x describe-input-method @key{RET} purescript-unicode}. A sequence like @samp{<=} is ambiguous and can mean either @samp{⇐} or @samp{≤}. Typing it presents you with a choice. Type @kbd{1} or @kbd{2} to select an option or keep typing to use the default option. 172 | 173 | If you don't like the highlighting of partially matching tokens you can turn it off by setting @code{input-method-highlight-flag} to @code{nil} via @kbd{M-x customize-variable}. 174 | 175 | @node Indentation 176 | @chapter Indentation 177 | 178 | @cindex indentation 179 | @cindex layout rule 180 | @cindex off-side rule 181 | 182 | For general information about indentation support in GNU Emacs, @pxref{Indentation,,,emacs}. 183 | 184 | In PureScript, code indentation has semantic meaning as it defines the block structure@footnote{PureScript also supports braces and semicolons notation for conveying the block structure. However, most PureScript programs written by humans use indentation for block structuring.}. 185 | 186 | As GNU Emacs' default indentation function (i.e. @code{indent-relative}) is not designed for use with PureScript's layout rule, PureScript mode includes three different indentation minor modes with different trade-offs: 187 | 188 | @ftable @code 189 | 190 | @item turn-on-purescript-simple-indent 191 | 192 | A very simple indentation scheme; In this scheme, @key{TAB} will now move the cursor to the next indent point in the previous non-blank line. An indent point is a non-whitespace character following whitespace. 193 | 194 | @item turn-on-purescript-indent 195 | 196 | Intelligent semi-automatic indentation for PureScript's layout rule. The basic idea is to have @key{TAB} cycle through possibilities indentation points based on some clever heuristics. 197 | 198 | The rationale and the implementation principles are described in more detail in the article @cite{Dynamic tabbing for automatic indentation with the layout rule} published in the Journal of Functional Programming 8.5 (1998). 199 | 200 | @item turn-on-purescript-indentation 201 | 202 | Improved variation of @code{turn-on-purescript-indent} indentation mode. Rebinds @key{RET} and @key{DEL}, so that indentations can be set and deleted as if they were real tabs. 203 | 204 | @end ftable 205 | 206 | To enable one of these three mutually exclusive indentation schemes, you just need call one (and only one!) of the @code{turn-on-*} commands while in the buffer you want the indentation scheme to be activated for. 207 | 208 | The recommended way is to add one of @code{turn-on-*} commands to @code{purescript-mode-hook}. This can be done either by using @kbd{M-x customize-variable @key{RET} purescript-mode-hook} which provides a convenient user interface or by adding @emph{one} of the following three lines to your @file{.emacs} file: 209 | 210 | @lisp 211 | (add-hook 'purescript-mode-hook 'turn-on-purescript-simple-indent) 212 | (add-hook 'purescript-mode-hook 'turn-on-purescript-indent) 213 | (add-hook 'purescript-mode-hook 'turn-on-purescript-indentation) 214 | @end lisp 215 | 216 | @section Interactive Block Indentation 217 | 218 | By inserting the key bindings for @kbd{C-,} and @kbd{C-.} (these bindings are convenient on keyboard layouts where @key{,} and @key{.} are adjacent keys) as shown below you can interactively de/indent either the following nested block or, if a region is active while in Transient Mark Mode (@pxref{Disabled Transient Mark,,,emacs}), de/indent the active region. 219 | 220 | By using a numeric prefix argument (@pxref{Prefix Command Arguments,,,elisp}) you can modify the shift-amount; for instance, @kbd{C-u C-,} increases indentation by 4 characters at once. 221 | 222 | @findex purescript-move-nested-left 223 | @findex purescript-move-nested-right 224 | 225 | @lisp 226 | (eval-after-load "purescript-mode" 227 | '(progn 228 | (define-key purescript-mode-map (kbd "C-,") 'purescript-move-nested-left) 229 | (define-key purescript-mode-map (kbd "C-.") 'purescript-move-nested-right))) 230 | @end lisp 231 | 232 | @section Rectangle Commands 233 | 234 | @cindex rectangle 235 | @cindex CUA mode 236 | 237 | GNU Emacs provides so-called @dfn{rectangle commands} which operate on rectangular areas of text, which are particularly useful for languages with a layout rule such as PureScript. @xref{Rectangles,,,emacs}, to learn more about rectangle commands. 238 | 239 | Moreover, CUA mode (@pxref{CUA Bindings,,,emacs}) provides enhanced rectangle support with visible rectangle highlighting. When CUA mode is active, you can initiate a rectangle selection by @kbd{C-RET} and extend it simply by movement commands. You don't have to enable full CUA mode to benefit from these enhanced rectangle commands; you can activate CUA selection mode (without redefining @kbd{C-x},@kbd{C-c},@kbd{C-v}, and @kbd{C-z}) by calling @kbd{M-x cua-selection-mode} (or adding @code{(cua-selection-mode nil)} to your @code{purescript-mode-hook}). 240 | 241 | @node purescript-decl-scan-mode 242 | @chapter @code{purescript-decl-scan-mode} 243 | 244 | @findex turn-on-purescript-decl-scan 245 | @findex purescript-decl-scan-mode 246 | @vindex purescript-decl-scan-mode-hook 247 | 248 | @code{purescript-decl-scan-mode} is a minor mode which performs declaration scanning and provides @kbd{M-x imenu} support (@pxref{Imenu,,,emacs} for more information). 249 | 250 | For non-literate and @TeX{}-style literate scripts, the common convention that top-level declarations start at the first column is assumed. For Bird-style literate scripts, the common convention that top-level declarations start at the third column, ie. after @samp{> }, is assumed. 251 | 252 | When @code{purescript-decl-scan-mode} is active, the standard Emacs top-level definition movement commands (@pxref{Moving by Defuns,,,emacs}) are enabled to operate on PureScript declarations: 253 | 254 | @table @kbd 255 | @item C-M-a 256 | Move to beginning of current or preceding declaration (@code{beginning-of-defun}). 257 | 258 | @item C-M-e 259 | Move to end of current or following declaration (@code{end-of-defun}). 260 | 261 | @item C-M-h 262 | Select whole current or following declaration (@code{mark-defun}). 263 | @end table 264 | 265 | Moreover, if enabled via the option @code{purescript-decl-scan-add-to-menubar}, a menu item ``Declarations'' is added to the menu bar listing the scanned declarations and allowing to jump to declarations in the source buffer. 266 | 267 | It's recommended to have font lock mode enabled (@pxref{Font Lock,,,emacs}) as @code{purescript-decl-scan-mode} ignores text highlighted with @code{font-lock-comment-face}. 268 | 269 | As usual, in order to activate @code{purescript-decl-scan-mode} automatically for PureScript buffers, add @code{turn-on-purescript-decl-scan} to @code{purescript-mode-hook}: 270 | 271 | @lisp 272 | (add-hook 'purescript-mode-hook 'turn-on-purescript-decl-scan) 273 | @end lisp 274 | 275 | @code{purescript-decl-scan-mode} enables the use of features that build upon @code{imenu} support such as Speedbar Frames (@pxref{Speedbar,,,emacs}) or the global ``Which Function'' minor mode (@pxref{Which Function,,,emacs}). 276 | 277 | In order to enable @code{which-function-mode} for PureScript buffers you need to add the following to your Emacs initialization: 278 | 279 | @lisp 280 | (eval-after-load "which-func" 281 | '(add-to-list 'which-func-modes 'purescript-mode)) 282 | @end lisp 283 | 284 | @node Compilation 285 | @chapter Compilation 286 | 287 | @findex purescript-compile 288 | 289 | PureScript mode comes equipped with a specialized @dfn{Compilation mode} tailored to GHC's compiler messages with optional support for Cabal projects. @xref{Compilation Mode,,,emacs}, for more information about the basic commands provided by the Compilation mode which are available in the PureScript compilation sub-mode as well. The additional features provided compared to Emacs' basic Compilation mode are: 290 | 291 | @itemize 292 | @item 293 | DWIM-style auto-detection of compile command (including support for CABAL projects) 294 | @item 295 | Support for GHC's compile messages and recognizing error, warning and info source locations (including @option{-ferror-spans} syntax) 296 | @item 297 | Support for filtering out GHC's uninteresting @samp{Loading package...} linker messages 298 | @end itemize 299 | 300 | In order to use it, invoke the @code{purescript-compile} command instead of @code{compile} as you would for the ordinary Compilation mode. It's recommended to bind @code{purescript-compile} to a convenient key binding. For instance, you can add the following to your Emacs initialization to bind @code{purescript-compile} to @kbd{C-c C-c}. 301 | 302 | @lisp 303 | (eval-after-load "purescript-mode" 304 | '(define-key purescript-mode-map (kbd "C-c C-c") 'purescript-compile)) 305 | 306 | (eval-after-load "purescript-cabal" 307 | '(define-key purescript-cabal-mode-map (kbd "C-c C-c") 'purescript-compile)) 308 | @end lisp 309 | 310 | @noindent 311 | The following description assumes that @code{purescript-compile} has been bound to @kbd{C-c C-c}. 312 | 313 | @vindex purescript-compile-cabal-build-command 314 | @vindex purescript-compile-cabal-build-command-alt 315 | @vindex purescript-compile-command 316 | 317 | When invoked, @code{purescript-compile} tries to guess how to compile the PureScript program your currently visited buffer belongs to, by searching for a @file{.cabal} file in the current of enclosing parent folders. If a @file{.cabal} file was found, the command defined in the @code{purescript-compile-cabal-build-command} option is used. Moreover, when requesting to compile a @file{.cabal}-file is detected and a negative prefix argument (e.g. @kbd{C-- C-c C-c}) was given, the alternative @code{purescript-compile-cabal-build-command-alt} is invoked. By default, @code{purescript-compile-cabal-build-command-alt} contains a @samp{cabal clean -s} command in order to force a full rebuild. 318 | 319 | Otherwise if no @file{.cabal} could be found, a single-module compilation is assumed and @code{purescript-compile-command} is used (@emph{if} the currently visited buffer contains PureScript source code). 320 | 321 | You can also inspect and modify the compile command to be invoked temporarily by invoking @code{purescript-compile} with a prefix argument (e.g. @kbd{C-u C-c C-c}). If later-on you want to recompile using the same customized compile command, invoke @code{recompile} (bound to @kbd{g}) inside the @samp{*purescript-compilation*} buffer. 322 | 323 | @node inferior-purescript-mode 324 | @chapter @code{inferior-purescript-mode} 325 | 326 | @findex inferior-purescript-find-definition 327 | @findex inferior-purescript-find-haddock 328 | @findex inferior-purescript-info 329 | @findex inferior-purescript-load-and-run 330 | @findex inferior-purescript-load-file 331 | @findex inferior-purescript-mode 332 | @findex inferior-purescript-reload-file 333 | @findex inferior-purescript-start-process 334 | @findex inferior-purescript-type 335 | @vindex purescript-program-name 336 | @vindex inferior-purescript-mode-hook 337 | 338 | The major mode @code{inferior-purescript-mode} provides support for interacting with an inferior PureScript process based on @code{comint-mode}. 339 | 340 | By default the @code{purescript-mode-map} keymap is setup to use this mode: 341 | 342 | @table @kbd 343 | @item C-c C-z 344 | is bound to @code{switch-to-purescript} 345 | @item C-c C-b 346 | is bound to @code{switch-to-purescript} 347 | @item C-c C-l 348 | is bound to @code{inferior-purescript-load-file} 349 | @item C-c C-t 350 | is bound to @code{inferior-purescript-type} 351 | @item C-c C-i 352 | is bound to @code{inferior-purescript-info} 353 | @end table 354 | 355 | The PureScript interpreter used by the inferior PureScript mode is auto-detected by default, but is customizable via the @code{purescript-program-name} variable. 356 | 357 | Currently, GHCi and Hugs are support as PureScript interpreter. 358 | 359 | TODO/WRITEME 360 | @c write about supported features 361 | 362 | @node purescript-interactive-mode 363 | @chapter @code{purescript-interactive-mode} 364 | 365 | An alternative mode providing a @acronym{REPL,read–eval–print loop} via GHCi sessions is called @code{purescript-interactive-mode}, which effectively replaces @code{inferior-purescript-mode}, but comes with a different set of features: 366 | 367 | @itemize 368 | @item 369 | Separate sessions per Cabal project @file{purescript-session.el}. 370 | @item 371 | A new inferior PureScript process handling code @file{purescript-process.el}. 372 | @item 373 | New REPL implementation similiar to SLIME/IELM @file{purescript-interactive-mode.el}. 374 | @end itemize 375 | 376 | In order to use @code{purescript-interactive-mode} instead of the default @code{inferior-purescript-mode}, you need to replace some of the default keybindings in the @code{purescript-mode-map} keymap with the respective @code{purescript-interactive-mode} counterparts: 377 | 378 | @lisp 379 | (eval-after-load "purescript-mode" 380 | '(progn 381 | (define-key purescript-mode-map (kbd "C-x C-d") nil) 382 | (define-key purescript-mode-map (kbd "C-c C-z") 'purescript-interactive-switch) 383 | (define-key purescript-mode-map (kbd "C-c C-l") 'purescript-process-load-file) 384 | (define-key purescript-mode-map (kbd "C-c C-b") 'purescript-interactive-switch) 385 | (define-key purescript-mode-map (kbd "C-c C-t") 'purescript-process-do-type) 386 | (define-key purescript-mode-map (kbd "C-c C-i") 'purescript-process-do-info) 387 | (define-key purescript-mode-map (kbd "C-c M-.") nil) 388 | (define-key purescript-mode-map (kbd "C-c C-d") nil))) 389 | @end lisp 390 | 391 | With @code{purescript-interactive-mode}, each PureScript source buffer is associated with at most one GHCi session, so when you call @code{purescript-process-load-file} for a PureScript source buffer which has no session associated yet, you're asked which GHCi session to create or associate with. 392 | 393 | TODO/WRITEME 394 | 395 | @node purescript-cabal-mode 396 | @chapter @code{purescript-cabal-mode} 397 | 398 | @findex purescript-cabal-mode 399 | @vindex purescript-cabal-mode-hook 400 | 401 | @code{purescript-cabal-mode} is a major mode for editing @uref{http://www.purescript.org/cabal/users-guide/developing-packages.html,Cabal package description files} and is automatically associated with files having a @file{.cabal} extension. 402 | 403 | @findex purescript-cabal-visit-file 404 | 405 | For quickly locating and jumping to the nearest @file{.cabal} file from a PureScript source buffer, you can use @kbd{M-x purescript-cabal-visit-file}; with a prefix argument (i.e. @kbd{C-u}) @code{find-file-other-window} is used to visit the @file{.cabal} file. If you wish, you can bind @code{purescript-cabal-visit-file} to a convenient key sequence, e.g. 406 | 407 | @lisp 408 | (eval-after-load "purescript-mode" 409 | (define-key purescript-mode-map (kbd "C-c v c") 'purescript-cabal-visit-file)) 410 | @end lisp 411 | 412 | TODO/WRITEME 413 | 414 | @node Concept index 415 | @unnumbered Concept index 416 | 417 | @printindex cp 418 | 419 | @node Function index 420 | @unnumbered Function index 421 | 422 | @printindex fn 423 | 424 | @node Variable index 425 | @unnumbered Variable index 426 | 427 | @printindex vr 428 | 429 | @bye 430 | 431 | @c Local Variables: 432 | @c eval: (visual-line-mode t) 433 | @c eval: (linum-mode t) 434 | @c eval: (hl-line-mode t) 435 | @c End: 436 | -------------------------------------------------------------------------------- /purescript-decl-scan.el: -------------------------------------------------------------------------------- 1 | ;;; purescript-decl-scan.el --- Declaration scanning module for PureScript Mode -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2004, 2005, 2007, 2009 Free Software Foundation, Inc. 4 | ;; Copyright (C) 1997-1998 Graeme E Moss 5 | 6 | ;; Author: 1997-1998 Graeme E Moss 7 | ;; Maintainer: Stefan Monnier 8 | ;; Keywords: declarations menu files PureScript 9 | ;; URL: http://cvs.purescript.org/cgi-bin/cvsweb.cgi/fptools/CONTRIB/purescript-modes/emacs/purescript-decl-scan.el?rev=HEAD 10 | 11 | ;; This file is not part of GNU Emacs. 12 | 13 | ;; This file is free software; you can redistribute it and/or modify 14 | ;; it under the terms of the GNU General Public License as published by 15 | ;; the Free Software Foundation; either version 3, or (at your option) 16 | ;; any later version. 17 | 18 | ;; This file is distributed in the hope that it will be useful, 19 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | ;; GNU General Public License for more details. 22 | 23 | ;; You should have received a copy of the GNU General Public License 24 | ;; along with this program. If not, see . 25 | 26 | ;;; Commentary: 27 | 28 | ;; Purpose: 29 | ;; 30 | ;; Top-level declarations are scanned and placed in a menu. Supports 31 | ;; full Latin1 PureScript 1.4 as well as literate scripts. 32 | ;; 33 | ;; 34 | ;; Installation: 35 | ;; 36 | ;; To turn declaration scanning on for all PureScript buffers under the 37 | ;; PureScript mode of Moss&Thorn, add this to .emacs: 38 | ;; 39 | ;; (add-hook 'purescript-mode-hook 'turn-on-purescript-decl-scan) 40 | ;; 41 | ;; Otherwise, call `turn-on-purescript-decl-scan'. 42 | ;; 43 | ;; 44 | ;; Customisation: 45 | ;; 46 | ;; M-x customize-group purescript-decl-scan 47 | ;; 48 | ;; 49 | ;; History: 50 | ;; 51 | ;; If you have any problems or suggestions, after consulting the list 52 | ;; below, email gem@cs.york.ac.uk quoting the version of the library 53 | ;; you are using, the version of Emacs you are using, and a small 54 | ;; example of the problem or suggestion. Note that this library 55 | ;; requires a reasonably recent version of Emacs. 56 | ;; 57 | ;; Uses `imenu' under Emacs. 58 | ;; 59 | ;; Version 1.2: 60 | ;; Added support for LaTeX-style literate scripts. 61 | ;; 62 | ;; Version 1.1: 63 | ;; Use own syntax table. Fixed bug for very small buffers. Use 64 | ;; markers instead of pointers (markers move with the text). 65 | ;; 66 | ;; Version 1.0: 67 | ;; Brought over from PureScript mode v1.1. 68 | ;; 69 | ;; 70 | ;; Present Limitations/Future Work (contributions are most welcome!): 71 | ;; 72 | ;; . Declarations requiring information extending beyond starting line 73 | ;; don't get scanned properly, eg. 74 | ;; > class Eq a => 75 | ;; > Test a 76 | ;; 77 | ;; . Comments placed in the midst of the first few lexemes of a 78 | ;; declaration will cause havoc, eg. 79 | ;; > infixWithComments :: Int -> Int -> Int 80 | ;; > x {-nastyComment-} `infixWithComments` y = x + y 81 | ;; but are not worth worrying about. 82 | ;; 83 | ;; . Would be nice to scan other top-level declarations such as 84 | ;; methods of a class, datatype field labels... any more? 85 | ;; 86 | ;; . Support for GreenCard? 87 | ;; 88 | ;; . Re-running (literate-)purescript-imenu should not cause the problems 89 | ;; that it does. The ability to turn off scanning would also be 90 | ;; useful. (Note that re-running (literate-)purescript-mode seems to 91 | ;; cause no problems.) 92 | 93 | ;; All functions/variables start with 94 | ;; `(turn-(on/off)-)purescript-decl-scan' or `purescript-ds-'. 95 | 96 | ;; The imenu support is based on code taken from `hugs-mode', 97 | ;; thanks go to Chris Van Humbeeck. 98 | 99 | ;; Version. 100 | 101 | ;;; Code: 102 | 103 | (require 'purescript-mode) 104 | (require 'syntax) 105 | (require 'cl-lib) 106 | (require 'imenu) 107 | (require 'subr-x) 108 | 109 | (defgroup purescript-decl-scan nil 110 | "PureScript declaration scanning (`imenu' support)." 111 | :link '(custom-manual "(purescript-mode)purescript-decl-scan-mode") 112 | :group 'purescript 113 | :prefix "purescript-decl-scan-") 114 | 115 | (defcustom purescript-decl-scan-bindings-as-variables nil 116 | "Whether to put top-level value bindings into a \"Variables\" category." 117 | :group 'purescript-decl-scan 118 | :type 'boolean) 119 | 120 | (defcustom purescript-decl-scan-add-to-menubar t 121 | "Whether to add a \"Declarations\" menu entry to menu bar." 122 | :group 'purescript-decl-scan 123 | :type 'boolean) 124 | 125 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 126 | ;; General declaration scanning functions. 127 | 128 | (defvar purescript-ds-start-keywords-re 129 | (regexp-opt '("class" "data" "derive instance" "import" "instance" "infixl" "infixr" "module" "primitive" "type" "newtype") 'words) 130 | "Keywords that may start a declaration.") 131 | 132 | (defvar purescript-ds-syntax-table 133 | (let ((table (copy-syntax-table purescript-mode-syntax-table))) 134 | (modify-syntax-entry ?\' "w" table) 135 | (modify-syntax-entry ?_ "w" table) 136 | (modify-syntax-entry ?\\ "_" table) 137 | table) 138 | "Syntax table used for PureScript declaration scanning.") 139 | 140 | 141 | (defun purescript-ds-get-variable (prefix) 142 | "Return variable involved in value binding or type signature. 143 | Assumes point is looking at the regexp PREFIX followed by the 144 | start of a declaration (perhaps in the middle of a series of 145 | declarations concerning a single variable). Otherwise return nil. 146 | Point is not changed." 147 | ;; I think I can now handle all declarations bar those with comments 148 | ;; nested before the second lexeme. 149 | (save-excursion 150 | (with-syntax-table purescript-ds-syntax-table 151 | (if (looking-at prefix) (goto-char (match-end 0))) 152 | ;; Keyword. 153 | (if (looking-at purescript-ds-start-keywords-re) 154 | nil 155 | (or ;; Parenthesized symbolic variable. 156 | (and (looking-at "(\\(\\s_+\\))") (match-string-no-properties 1)) 157 | ;; General case. 158 | (if (looking-at 159 | (if (eq ?\( (char-after)) 160 | ;; Skip paranthesised expression. 161 | (progn 162 | (forward-sexp) 163 | ;; Repeating this code and avoiding moving point if 164 | ;; possible speeds things up. 165 | "\\(\\'\\)?\\s-*\\(\\s_+\\|`\\(\\sw+\\)`\\)") 166 | "\\(\\sw+\\)?\\s-*\\(\\s_+\\|`\\(\\sw+\\)`\\)")) 167 | (let ((match2 (match-string-no-properties 2))) 168 | ;; Weed out `::', `∷',`=' and `|' from potential infix 169 | ;; symbolic variable. 170 | (if (member match2 '("::" "∷" "=" "|")) 171 | ;; Variable identifier. 172 | (match-string-no-properties 1) 173 | (if (eq (aref match2 0) ?\`) 174 | ;; Infix variable identifier. 175 | (match-string-no-properties 3) 176 | ;; Infix symbolic variable. 177 | match2)))) 178 | ;; Variable identifier. 179 | (and (looking-at "\\sw+") (match-string-no-properties 0))))))) 180 | 181 | (defun purescript-ds-move-to-start-regexp (inc regexp) 182 | "Move to beginning of line that succeeds/precedes (INC = 1/-1) 183 | current line that starts with REGEXP and is not in `font-lock-comment-face'." 184 | ;; Making this defsubst instead of defun appears to have little or 185 | ;; no effect on efficiency. It is probably not called enough to do 186 | ;; so. 187 | (while (and (= (forward-line inc) 0) 188 | (or (not (looking-at regexp)) 189 | (eq (get-text-property (point) 'face) 190 | 'font-lock-comment-face))))) 191 | 192 | (defun purescript-ds-move-to-start-regexp-skipping-comments (inc regexp) 193 | "Like purescript-ds-move-to-start-regexp, but uses syntax-ppss to 194 | skip comments" 195 | (let (p) 196 | (cl-loop 197 | do (setq p (point)) 198 | (purescript-ds-move-to-start-regexp inc regexp) 199 | while (and (nth 4 (syntax-ppss)) 200 | (/= p (point)))))) 201 | 202 | (defvar literate-purescript-ds-line-prefix "> ?" 203 | "Regexp matching start of a line of Bird-style literate code. 204 | Current value is \"> \" as we assume top-level declarations start 205 | at column 3. Must not contain the special \"^\" regexp as we may 206 | not use the regexp at the start of a regexp string. Note this is 207 | only for `imenu' support.") 208 | 209 | (defvar purescript-ds-start-decl-re "\\(\\sw\\|(\\)" 210 | "The regexp that starts a PureScript declaration.") 211 | 212 | (defvar literate-purescript-ds-start-decl-re 213 | (concat literate-purescript-ds-line-prefix purescript-ds-start-decl-re) 214 | "The regexp that starts a Bird-style literate PureScript declaration.") 215 | 216 | (defun purescript-ds-move-to-decl (direction bird-literate fix) 217 | "General function for moving to the start of a declaration, 218 | either forwards or backwards from point, with normal or with Bird-style 219 | literate scripts. If DIRECTION is t, then forward, else backward. If 220 | BIRD-LITERATE is t, then treat as Bird-style literate scripts, else 221 | normal scripts. Returns point if point is left at the start of a 222 | declaration, and nil otherwise, ie. because point is at the beginning 223 | or end of the buffer and no declaration starts there. If FIX is t, 224 | then point does not move if already at the start of a declaration." 225 | ;; As `purescript-ds-get-variable' cannot separate an infix variable 226 | ;; identifier out of a value binding with non-alphanumeric first 227 | ;; argument, this function will treat such value bindings as 228 | ;; separate from the declarations surrounding it. 229 | (let ( ;; The variable typed or bound in the current series of 230 | ;; declarations. 231 | name 232 | ;; The variable typed or bound in the new declaration. 233 | newname 234 | ;; Hack to solve hard problem for Bird-style literate scripts 235 | ;; that start with a declaration. We are in the abyss if 236 | ;; point is before start of this declaration. 237 | abyss 238 | (line-prefix (if bird-literate literate-purescript-ds-line-prefix "")) 239 | ;; The regexp to match for the start of a declaration. 240 | (start-decl-re (if bird-literate 241 | literate-purescript-ds-start-decl-re 242 | purescript-ds-start-decl-re)) 243 | (increment (if direction 1 -1)) 244 | (bound (if direction (point-max) (point-min)))) 245 | ;; Change syntax table. 246 | (with-syntax-table purescript-ds-syntax-table 247 | ;; move to beginning of line that starts the "current 248 | ;; declaration" (dependent on DIRECTION and FIX), and then get 249 | ;; the variable typed or bound by this declaration, if any. 250 | (let ( ;; Where point was at call of function. 251 | (here (point)) 252 | ;; Where the declaration on this line (if any) starts. 253 | (start (progn 254 | (beginning-of-line) 255 | ;; Checking the face to ensure a declaration starts 256 | ;; here seems to be the only addition to make this 257 | ;; module support LaTeX-style literate scripts. 258 | (if (and (looking-at start-decl-re) 259 | (not (eq (get-text-property (point) 'face) 260 | 'font-lock-comment-face))) 261 | (match-beginning 1))))) 262 | (if (and start 263 | ;; This complicated boolean determines whether we 264 | ;; should include the declaration that starts on the 265 | ;; current line as the "current declaration" or not. 266 | (or (and (or (and direction (not fix)) 267 | (and (not direction) fix)) 268 | (>= here start)) 269 | (and (or (and direction fix) 270 | (and (not direction) (not fix))) 271 | (> here start)))) 272 | ;; If so, we are already at start of the current line, so 273 | ;; do nothing. 274 | () 275 | ;; If point was before start of a declaration on the first 276 | ;; line of the buffer (possible for Bird-style literate 277 | ;; scripts) then we are in the abyss. 278 | (if (and start (bobp)) 279 | (setq abyss t) 280 | ;; Otherwise we move to the start of the first declaration 281 | ;; on a line preceding the current one, skipping comments 282 | (purescript-ds-move-to-start-regexp-skipping-comments -1 start-decl-re)))) 283 | ;; If we are in the abyss, position and return as appropriate. 284 | (if abyss 285 | (if (not direction) 286 | nil 287 | (re-search-forward (concat "\\=" line-prefix) nil t) 288 | (point)) 289 | ;; Get the variable typed or bound by this declaration, if any. 290 | (setq name (purescript-ds-get-variable line-prefix)) 291 | (if (not name) 292 | ;; If no such variable, stop at the start of this 293 | ;; declaration if moving backward, or move to the next 294 | ;; declaration if moving forward. 295 | (if direction 296 | (purescript-ds-move-to-start-regexp-skipping-comments 1 start-decl-re)) 297 | ;; If there is a variable, find the first 298 | ;; succeeding/preceding declaration that does not type or 299 | ;; bind it. Check for reaching start/end of buffer and 300 | ;; comments. 301 | (purescript-ds-move-to-start-regexp-skipping-comments increment start-decl-re) 302 | (while (and (/= (point) bound) 303 | (and (setq newname (purescript-ds-get-variable line-prefix)) 304 | (string= name newname))) 305 | (setq name newname) 306 | (purescript-ds-move-to-start-regexp-skipping-comments increment start-decl-re)) 307 | ;; If we are going backward, and have either reached a new 308 | ;; declaration or the beginning of a buffer that does not 309 | ;; start with a declaration, move forward to start of next 310 | ;; declaration (which must exist). Otherwise, we are done. 311 | (if (and (not direction) 312 | (or (and (looking-at start-decl-re) 313 | (not (string= name 314 | ;; Note we must not use 315 | ;; newname here as this may 316 | ;; not have been set if we 317 | ;; have reached the beginning 318 | ;; of the buffer. 319 | (purescript-ds-get-variable 320 | line-prefix)))) 321 | (and (not (looking-at start-decl-re)) 322 | (bobp)))) 323 | (purescript-ds-move-to-start-regexp-skipping-comments 1 start-decl-re))) 324 | ;; Store whether we are at the start of a declaration or not. 325 | ;; Used to calculate final result. 326 | (let ((at-start-decl (looking-at start-decl-re))) 327 | ;; If we are at the beginning of a line, move over 328 | ;; line-prefix, if present at point. 329 | (if (bolp) 330 | (re-search-forward (concat "\\=" line-prefix) (point-max) t)) 331 | ;; Return point if at the start of a declaration and nil 332 | ;; otherwise. 333 | (if at-start-decl (point) nil)))))) 334 | 335 | (defun purescript-ds-bird-p () 336 | (and (boundp 'purescript-literate) (eq purescript-literate 'bird))) 337 | 338 | (defun purescript-ds-backward-decl () 339 | "Move backward to the first character that starts a top-level declaration. 340 | A series of declarations concerning one variable is treated as one 341 | declaration by this function. So, if point is within a top-level 342 | declaration then move it to the start of that declaration. If point 343 | is already at the start of a top-level declaration, then move it to 344 | the start of the preceding declaration. Returns point if point is 345 | left at the start of a declaration, and nil otherwise, ie. because 346 | point is at the beginning of the buffer and no declaration starts 347 | there." 348 | (interactive) 349 | (purescript-ds-move-to-decl nil (purescript-ds-bird-p) nil)) 350 | 351 | (defun purescript-ds-forward-decl () 352 | "As `purescript-ds-backward-decl' but forward." 353 | (interactive) 354 | (purescript-ds-move-to-decl t (purescript-ds-bird-p) nil)) 355 | 356 | (defun purescript-ds-generic-find-next-decl (bird-literate) 357 | "Find the name, position and type of the declaration at or after point. 358 | Return ((NAME . (START-POSITION . NAME-POSITION)) . TYPE) 359 | if one exists and nil otherwise. The start-position is at the start 360 | of the declaration, and the name-position is at the start of the name 361 | of the declaration. The name is a string, the positions are buffer 362 | positions and the type is one of the symbols \"variable\", \"datatype\", 363 | \"class\", \"import\" and \"instance\"." 364 | (let ( ;; The name, type and name-position of the declaration to 365 | ;; return. 366 | name 367 | type 368 | name-pos 369 | ;; Buffer positions marking the start and end of the space 370 | ;; containing a declaration. 371 | start 372 | end) 373 | ;; Change to declaration scanning syntax. 374 | (with-syntax-table purescript-ds-syntax-table 375 | ;; Stop when we are at the end of the buffer or when a valid 376 | ;; declaration is grabbed. 377 | (while (not (or (eobp) name)) 378 | ;; Move forward to next declaration at or after point. 379 | (purescript-ds-move-to-decl t bird-literate t) 380 | ;; Start and end of search space is currently just the starting 381 | ;; line of the declaration. 382 | (setq start (point) 383 | end (line-end-position)) 384 | (cond 385 | ;; If the start of the top-level declaration does not begin 386 | ;; with a starting keyword, then (if legal) must be a type 387 | ;; signature or value binding, and the variable concerned is 388 | ;; grabbed. 389 | ((not (looking-at purescript-ds-start-keywords-re)) 390 | (setq name (purescript-ds-get-variable "")) 391 | (if name 392 | (progn 393 | (setq type 'variable) 394 | (re-search-forward (regexp-quote name) end t) 395 | (setq name-pos (match-beginning 0))))) 396 | ;; User-defined datatype declaration. 397 | ((re-search-forward "\\=\\(data\\|newtype\\|type\\)\\>" end t) 398 | (re-search-forward "=>" end t) 399 | (if (looking-at "[ \t]*\\(\\sw+\\)") 400 | (progn 401 | (setq name (match-string-no-properties 1)) 402 | (setq name-pos (match-beginning 1)) 403 | (setq type 'datatype)))) 404 | ;; Class declaration. 405 | ((re-search-forward "\\=class\\>" end t) 406 | (re-search-forward "=>" end t) 407 | (if (looking-at "[ \t]*\\(\\sw+\\)") 408 | (progn 409 | (setq name (match-string-no-properties 1)) 410 | (setq name-pos (match-beginning 1)) 411 | (setq type 'class)))) 412 | ;; Import declaration. 413 | ((looking-at "import[ \t]+\\(?:safe[\t ]+\\)?\\(?:qualified[ \t]+\\)?\\(?:\"[^\"]*\"[\t ]+\\)?\\(\\(?:\\sw\\|.\\)+\\)") 414 | (setq name (match-string-no-properties 1)) 415 | (setq name-pos (match-beginning 1)) 416 | (setq type 'import)) 417 | ;; Instance declaration. 418 | ((re-search-forward "\\=\\(instance\\|derive instance\\)[ \t]+" end t) 419 | (re-search-forward "=>[ \t]+" end t) 420 | ;; The instance "title" starts just after the `instance' (and 421 | ;; any context) and finishes just before the _first_ `where' 422 | ;; if one exists. This solution is ugly, but I can't find a 423 | ;; nicer one---a simple regexp will pick up the last `where', 424 | ;; which may be rare but nevertheless... 425 | (setq name-pos (point)) 426 | (setq name (buffer-substring-no-properties 427 | (point) 428 | (progn 429 | ;; Look for a `where'. 430 | (if (re-search-forward "\\" end t) 431 | ;; Move back to just before the `where'. 432 | (progn 433 | (re-search-backward "\\s-where") 434 | (point)) 435 | ;; No `where' so move to last non-whitespace 436 | ;; before `end'. 437 | (progn 438 | (goto-char end) 439 | (skip-chars-backward " \t") 440 | (point)))))) 441 | ;; If we did not manage to extract a name, cancel this 442 | ;; declaration (eg. when line ends in "=> "). 443 | (if (string-match "^[ \t]*$" name) (setq name nil)) 444 | (setq type 'instance))) 445 | ;; Move past start of current declaration. 446 | (goto-char end)) 447 | ;; If we have a valid declaration then return it, otherwise return 448 | ;; nil. 449 | (if name 450 | (cons (cons name (cons (copy-marker start t) (copy-marker name-pos t))) 451 | type) 452 | nil)))) 453 | 454 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 455 | ;; Declaration scanning via `imenu'. 456 | 457 | (defmacro purescript-when-let (spec &rest body) 458 | "A wrapper to silence `when-let' deprecation warning" 459 | (if (fboundp 'when-let*) 460 | (list 'when-let* spec (macroexp-progn body)) 461 | (with-no-warnings (list 'when-let spec (macroexp-progn body))))) 462 | 463 | ;;;###autoload 464 | (defun purescript-ds-create-imenu-index () 465 | "Function for finding `imenu' declarations in PureScript mode. 466 | Finds all declarations (classes, variables, imports, instances and 467 | datatypes) in a PureScript file for the `imenu' package." 468 | ;; Each list has elements of the form `(INDEX-NAME . INDEX-POSITION)'. 469 | ;; These lists are nested using `(INDEX-TITLE . INDEX-ALIST)'. 470 | (let* ((bird-literate (purescript-ds-bird-p)) 471 | (index-alist '()) 472 | (imenu (make-hash-table :test 'equal)) 473 | ;; Variables for showing progress. 474 | (bufname (buffer-name)) 475 | (divisor-of-progress (max 1 (/ (buffer-size) 100))) 476 | ;; The result we wish to return. 477 | result) 478 | (goto-char (point-min)) 479 | ;; Loop forwards from the beginning of the buffer through the 480 | ;; starts of the top-level declarations. 481 | (while (< (point) (point-max)) 482 | (message "Scanning declarations in %s... (%3d%%)" bufname 483 | (/ (- (point) (point-min)) divisor-of-progress)) 484 | ;; Grab the next declaration. 485 | (setq result (purescript-ds-generic-find-next-decl bird-literate)) 486 | (if result 487 | ;; If valid, extract the components of the result. 488 | (let* ((name-posns (car result)) 489 | (name (car name-posns)) 490 | (posns (cdr name-posns)) 491 | (start-pos (car posns)) 492 | (type (cdr result))) 493 | (puthash type 494 | (cons (cons name start-pos) (gethash type imenu '())) 495 | imenu)))) 496 | ;; Now sort all the lists, label them, and place them in one list. 497 | (message "Sorting declarations in %s..." bufname) 498 | (dolist (type '((datatype . "Datatypes") (instance . "Instances") 499 | (import . "Imports") (class . "Classes"))) 500 | (purescript-when-let ((curr-alist (gethash (car type) imenu))) 501 | (push (cons (cdr type) 502 | (sort curr-alist 'purescript-ds-imenu-label-cmp)) 503 | index-alist))) 504 | (purescript-when-let ((var-alist (gethash 'variable imenu))) 505 | (if purescript-decl-scan-bindings-as-variables 506 | (push (cons "Variables" 507 | (sort var-alist 'purescript-ds-imenu-label-cmp)) 508 | index-alist) 509 | (setq index-alist (append index-alist 510 | (sort var-alist 'purescript-ds-imenu-label-cmp))))) 511 | (message "Sorting declarations in %s...done" bufname) 512 | ;; Return the alist. 513 | index-alist)) 514 | 515 | (defun purescript-ds-imenu-label-cmp (el1 el2) 516 | "Predicate to compare labels in lists from `purescript-ds-create-imenu-index'." 517 | (string< (car el1) (car el2))) 518 | 519 | (defun purescript-ds-imenu () 520 | "Install `imenu' for PureScript scripts." 521 | (setq imenu-create-index-function 'purescript-ds-create-imenu-index) 522 | (when purescript-decl-scan-add-to-menubar 523 | (imenu-add-to-menubar "Declarations"))) 524 | 525 | ;; The main functions to turn on declaration scanning. 526 | ;;;###autoload 527 | (defun turn-on-purescript-decl-scan () 528 | "Unconditionally activate `purescript-decl-scan-mode'." 529 | (interactive) 530 | (purescript-decl-scan-mode)) 531 | 532 | ;;;###autoload 533 | (define-minor-mode purescript-decl-scan-mode 534 | "Toggle PureScript declaration scanning minor mode on or off. 535 | With a prefix argument ARG, enable minor mode if ARG is 536 | positive, and disable it otherwise. If called from Lisp, enable 537 | the mode if ARG is omitted or nil, and toggle it if ARG is `toggle'. 538 | 539 | See also info node `(purescript-mode)purescript-decl-scan-mode' for 540 | more details about this minor mode. 541 | 542 | Top-level declarations are scanned and listed in the menu item 543 | \"Declarations\" (if enabled via option 544 | `purescript-decl-scan-add-to-menubar'). Selecting an item from this 545 | menu will take point to the start of the declaration. 546 | 547 | \\[beginning-of-defun] and \\[end-of-defun] move forward and backward to the start of a declaration. 548 | 549 | This may link with `purescript-doc-mode'. 550 | 551 | For non-literate and LaTeX-style literate scripts, we assume the 552 | common convention that top-level declarations start at the first 553 | column. For Bird-style literate scripts, we assume the common 554 | convention that top-level declarations start at the third column, 555 | ie. after \"> \". 556 | 557 | Anything in `font-lock-comment-face' is not considered for a 558 | declaration. Therefore, using PureScript font locking with comments 559 | coloured in `font-lock-comment-face' improves declaration scanning. 560 | 561 | Literate PureScript scripts are supported: If the value of 562 | `purescript-literate' (set automatically by `literate-purescript-mode') 563 | is `bird', a Bird-style literate script is assumed. If it is nil 564 | or `tex', a non-literate or LaTeX-style literate script is 565 | assumed, respectively. 566 | 567 | Invokes `purescript-decl-scan-mode-hook' on activation." 568 | :group 'purescript-decl-scan 569 | 570 | (kill-local-variable 'beginning-of-defun-function) 571 | (kill-local-variable 'end-of-defun-function) 572 | (kill-local-variable 'imenu-create-index-function) 573 | (unless purescript-decl-scan-mode 574 | ;; How can we cleanly remove the "Declarations" menu? 575 | (when purescript-decl-scan-add-to-menubar 576 | (local-set-key [menu-bar index] nil))) 577 | 578 | (when purescript-decl-scan-mode 579 | (set (make-local-variable 'beginning-of-defun-function) 580 | 'purescript-ds-backward-decl) 581 | (set (make-local-variable 'end-of-defun-function) 582 | 'purescript-ds-forward-decl) 583 | (purescript-ds-imenu))) 584 | 585 | 586 | ;; Provide ourselves: 587 | 588 | (provide 'purescript-decl-scan) 589 | 590 | ;;; purescript-decl-scan.el ends here 591 | --------------------------------------------------------------------------------