├── .github └── workflows │ └── check.yaml ├── CHANGELOG.md ├── README.md └── dhall-mode.el /.github/workflows/check.yaml: -------------------------------------------------------------------------------- 1 | name: check 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | 9 | jobs: 10 | check: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | emacs_version: 16 | - 27.1 17 | - 26.1 18 | - 26.2 19 | - 26.3 20 | ignore_warnings: 21 | - false 22 | include: 23 | - emacs_version: snapshot 24 | ignore_warnings: false 25 | steps: 26 | - uses: actions/checkout@v2 27 | - uses: purcell/setup-emacs@master 28 | with: 29 | version: ${{ matrix.emacs_version }} 30 | - uses: leotaku/elisp-check@master 31 | with: 32 | file: dhall-mode.el 33 | ignore_warnings: ${{ matrix.ignore_warnings }} 34 | warnings_as_errors: true 35 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.1.0 2 | 3 | * Initial version released 4 | * Supports automatic formatting 5 | * Basic syntax highlighting 6 | * Error reporting (via `dhall-format`) 7 | 8 | # 0.1.1 9 | 10 | * Proper multi line support 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dhall-mode 2 | 3 | [![MELPA](https://melpa.org/packages/dhall-mode-badge.svg)](https://melpa.org/#/dhall-mode) 4 | [![Build Status](https://github.com/psibi/dhall-mode/workflows/check/badge.svg)](https://github.com/psibi/dhall-mode/actions) 5 | 6 | Emacs Major mode for working 7 | with [Dhall](https://github.com/dhall-lang/dhall-lang) configuration 8 | language. 9 | 10 | ## Installation 11 | 12 | * Make sure that you 13 | install [dhall-format](https://github.com/dhall-lang/dhall-haskell) 14 | and it's PATH is available to emacs via `exec-path`. 15 | * Install this extension from MELPA: 16 | 17 | ``` emacs-lisp 18 | (use-package dhall-mode 19 | :ensure t 20 | :mode "\\.dhall\\'") 21 | ``` 22 | 23 | ## Demo 24 | 25 | ![Dhall in Emacs](https://user-images.githubusercontent.com/737477/31044377-e2af0e9e-a5eb-11e7-9757-806ae1448c40.gif "Dhall mode in Emacs") 26 | 27 | ## Features 28 | 29 | * Syntax highlighting (Using font lock) 30 | * Multiline support for String 31 | * Basic indendation, commenting 32 | * Automatic formatting on save (Configurable via variable). Uses [dhall-format](https://github.com/dhall-lang/dhall-haskell) for it. 33 | * Error highlighting. 34 | * REPL support. 35 | 36 | ## License 37 | 38 | Copyright © 2017 Sibi Prabakaran 39 | 40 | Distributed under GNU GPL, version 3. 41 | -------------------------------------------------------------------------------- /dhall-mode.el: -------------------------------------------------------------------------------- 1 | ;;; dhall-mode.el --- Major mode for the dhall configuration language -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2017 Sibi Prabakaran 4 | 5 | ;; Author: Sibi Prabakaran 6 | ;; Maintainer: Sibi Prabakaran 7 | ;; Keywords: languages 8 | ;; Version: 0.1.3 9 | ;; Package-Requires: ((emacs "24.4") (reformatter "0.3")) 10 | ;; URL: https://github.com/psibi/dhall-mode 11 | 12 | ;; This file is not part of GNU Emacs. 13 | 14 | ;; This file is free software; you can redistribute it and/or modify 15 | ;; it under the terms of the GNU General Public License as published by 16 | ;; the Free Software Foundation; either version 3, or (at your option) 17 | ;; any later version. 18 | 19 | ;; This file is distributed in the hope that it will be useful, 20 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | ;; GNU General Public License for more details. 23 | 24 | ;; You should have received a copy of the GNU General Public License 25 | ;; along with this program. If not, see . 26 | 27 | ;;; Commentary: 28 | 29 | ;; A major mode for editing Dhall configuration file (See 30 | ;; https://github.com/dhall-lang/dhall-lang to learn more) in Emacs. 31 | ;; 32 | ;; Some of its major features include: 33 | ;; 34 | ;; - syntax highlighting (font lock), 35 | ;; 36 | ;; - Basic indentation, multi line string support 37 | ;; 38 | ;; - Automatic formatting on save (configurable) 39 | ;; 40 | ;; - Error highlighting 41 | ;; 42 | ;;; Code: 43 | 44 | (require 'ansi-color) 45 | (require 'comint) 46 | (require 'reformatter) 47 | 48 | (defvar dhall-mode-map 49 | (let ((map (make-sparse-keymap))) 50 | (define-key map (kbd "C-c C-b") 'dhall-repl-show) 51 | (define-key map (kbd "C-c C-f") 'dhall-format-buffer) 52 | (define-key map (kbd "C-c C-t") 'dhall-buffer-type-show) 53 | map) 54 | "Keymap for using `dhall-mode'.") 55 | 56 | (defgroup dhall nil 57 | "Major mode for editing dhall files." 58 | :group 'languages 59 | :prefix "dhall-" 60 | :link '(url-link :tag "Site" "https://github.com/psibi/dhall-mode") 61 | :link '(url-link :tag "Repository" "https://github.com/psibi/dhall-mode")) 62 | 63 | ;; Create the syntax table for this mode. 64 | (defvar dhall-mode-syntax-table 65 | (let ((st (make-syntax-table))) 66 | ;; Taken from haskell-mode: https://stackoverflow.com/a/20845468/1651941 67 | (modify-syntax-entry ?\{ "(}1nb" st) 68 | (modify-syntax-entry ?\} "){4nb" st) 69 | (modify-syntax-entry ?- ". 123" st) 70 | (modify-syntax-entry ?\n ">" st) 71 | (modify-syntax-entry ?\ " " st) 72 | (modify-syntax-entry ?\t " " st) 73 | (modify-syntax-entry ?\[ "(]" st) 74 | (modify-syntax-entry ?\] ")[" st) 75 | (modify-syntax-entry ?\( "()" st) 76 | (modify-syntax-entry ?\) ")(" st) 77 | ;; Let us handle escapes and string 78 | (modify-syntax-entry ?\\ "." st) 79 | (modify-syntax-entry ?\" "." st) 80 | ;; End 81 | st) 82 | "Syntax table used while in `dhall-mode'.") 83 | 84 | ;; define several category of keywords 85 | (defvar dhall-mode-keywords (regexp-opt '("if" "then" "else" "let" "in" "using" "as") 'symbols)) 86 | 87 | (defvar dhall-mode-types 88 | (regexp-opt '("Optional" "Bool" "Natural" "Integer" "Double" "Text" "List" "Type" "Date" "Time" "TimeZone" "Bytes") 'symbols)) 89 | 90 | (defconst dhall-mode-constants (regexp-opt '("True" "False") 'symbols)) 91 | (defconst dhall-mode-numerals "\\_<[+\\-][1-9]+\\_>") 92 | (defconst dhall-mode-doubles "\\_<[+\\-]?[0-9]+\.[0-9]+\\_>") 93 | (defconst dhall-mode-operators (regexp-opt '("->" "\\[" "]" "," "++" "#" ":" "=" "==" "!=" "\\\\\(" "λ" "⫽" ")" "&&" "||" "{" "}" "("))) 94 | (defconst dhall-mode-variables "\\([a-zA-Z_][a-zA-Z_0-9\\-]*\\)[[:space:]]*=") 95 | (defconst dhall-mode-urls "\\_<\\(?:https?\\|file\\):[^[:space:]]+") 96 | (defconst dhall-mode-shas "\\_") 97 | (defconst dhall-mode-date "[0-9]\\{4\\}-[0-9][0-9]-[0-9][0-9]") 98 | 99 | (defconst dhall-mode-font-lock-keywords 100 | `( ;; Variables 101 | (,dhall-mode-urls . font-lock-function-name-face) 102 | (,dhall-mode-shas . font-lock-constant-face) 103 | (,dhall-mode-types . font-lock-type-face) 104 | (,dhall-mode-constants . font-lock-constant-face) 105 | (,dhall-mode-operators . font-lock-builtin-face) 106 | (,dhall-mode-variables . (1 font-lock-variable-name-face)) 107 | (,dhall-mode-keywords . font-lock-keyword-face) 108 | (,dhall-mode-date . font-lock-constant-face) 109 | (,dhall-mode-doubles . font-lock-constant-face) 110 | (,dhall-mode-numerals . font-lock-constant-face))) 111 | 112 | (defcustom dhall-command "dhall" 113 | "Command used to normalize Dhall files. 114 | Should be dhall or the complete path to your dhall executable, 115 | e.g.: /home/sibi/.local/bin/dhall" 116 | :type 'file 117 | :group 'dhall 118 | :safe 'stringp) 119 | 120 | (defcustom dhall-use-header-line t 121 | "If non-nil, display the type of the file in the window's header line." 122 | :type 'boolean 123 | :group 'dhall 124 | :safe 'booleanp) 125 | 126 | (defcustom dhall-format-command nil 127 | "Command used to format Dhall files. 128 | If your dhall command is old and does not support the \"format\" sub-command, 129 | then set this to \"dhall-format\". 130 | 131 | If specified, this should be the complete path to your dhall-format executable, 132 | e.g.: /home/sibi/.local/bin/dhall-format" 133 | :type 'file 134 | :group 'dhall 135 | :safe 'stringp) 136 | 137 | (defcustom dhall-format-at-save t 138 | "If non-nil, the Dhall buffers will be formatted after each save." 139 | :type 'boolean 140 | :group 'dhall 141 | :safe 'booleanp) 142 | 143 | (defcustom dhall-format-arguments nil 144 | "Provide a list of arguments for the formatter e.g. \='(\"--ascii\")." 145 | :type 'list 146 | :group 'dhall 147 | :safe 'listp) 148 | 149 | (defcustom dhall-freeze-arguments nil 150 | "Provide a list of arguments for freeze e.g. ='(\"--transitive\")." 151 | :type 'list 152 | :group 'dhall 153 | :safe 'listp) 154 | 155 | (defcustom dhall-lint-arguments nil 156 | "Provide a list of arguments for the linter e.g. \='(\"--transitive\")." 157 | :type 'list 158 | :group 'dhall 159 | :safe 'listp) 160 | 161 | (defcustom dhall-type-check-inactivity-timeout 1 162 | "Wait for this period of inactivity before refreshing the buffer type. 163 | You can try increasing this if type checking is slowing things 164 | down. You can also disable type-checking entirely by setting 165 | `dhall-use-header-line' to nil." 166 | :type 'number 167 | :group 'dhall 168 | :safe 'numberp) 169 | 170 | (defun dhall-buffer-type () 171 | "Return the type of the expression in the current buffer." 172 | ;; We resolve dhall-command in the current buffer, in case 173 | ;; dhall-command, exec-path or process-environment is local 174 | ;; there, so that we can propagate it to the temp buffer. 175 | (let ((cmd (executable-find dhall-command))) 176 | (when cmd 177 | (let ((errbuf (get-buffer-create "*dhall-buffer-type-errors*")) 178 | (source (buffer-string))) 179 | (with-temp-buffer 180 | (with-current-buffer errbuf 181 | (read-only-mode -1) 182 | (erase-buffer)) 183 | (insert source) 184 | (if (zerop (shell-command-on-region (point-min) 185 | (point-max) 186 | (concat cmd " type") 187 | nil t errbuf)) 188 | (replace-regexp-in-string "\\(?:\\` \\| \\'\\)" "" 189 | (replace-regexp-in-string "[[:space:]]+" " " (buffer-string))) 190 | (prog1 191 | nil 192 | (with-current-buffer errbuf 193 | (ansi-color-apply-on-region (point-min) (point-max)) 194 | (view-mode))))))))) 195 | 196 | (reformatter-define dhall-format 197 | :program (or dhall-format-command dhall-command) 198 | :args (append (unless dhall-format-command '("format")) dhall-format-arguments) 199 | :group 'dhall 200 | :lighter " DhFmt") 201 | 202 | (reformatter-define dhall-freeze 203 | :program dhall-command 204 | :args (append '("freeze") dhall-freeze-arguments) 205 | :group 'dhall 206 | :lighter " DhFreeze") 207 | 208 | (reformatter-define dhall-lint 209 | :program dhall-command 210 | :args (append '("lint") dhall-lint-arguments) 211 | :group 'dhall 212 | :lighter " DhLint") 213 | 214 | (defun dhall--get-string-type (parse-state) 215 | "Get the type of string based on PARSE-STATE." 216 | (let ((string-start (nth 8 parse-state))) 217 | (and string-start (get-text-property string-start 'dhall-string-type)))) 218 | 219 | (defun dhall--mark-string (pos string-type) 220 | "Mark string as a Dhall string. 221 | 222 | POS position of start of string 223 | STRING-TYPE type of string based off of Emacs syntax table types" 224 | (put-text-property pos (1+ pos) 225 | 'syntax-table (string-to-syntax "|")) 226 | (put-text-property pos (1+ pos) 227 | 'dhall-string-type string-type)) 228 | 229 | (defun dhall--double-quotes () 230 | "Handle Dhall double quotes." 231 | (let* ((pos (match-beginning 0))) 232 | (dhall--mark-string pos ?\"))) 233 | 234 | (defun dhall--single-quotes () 235 | "Handle Dhall single quotes." 236 | (let* ((pos (match-beginning 0))) 237 | (dhall--mark-string pos ?\"))) 238 | 239 | (defun dhall-syntax-propertize (start end) 240 | "Special syntax properties for Dhall from START to END." 241 | (goto-char start) 242 | (remove-text-properties start end '(syntax-table nil dhall-string-type nil)) 243 | (funcall 244 | (syntax-propertize-rules 245 | ("'\\{2,\\}" 246 | (0 (ignore (dhall--single-quotes)))) 247 | ("\"" 248 | (0 (ignore (dhall--double-quotes))))) 249 | start end)) 250 | 251 | (defvar-local dhall-buffer-type nil) 252 | (defvar-local dhall-buffer-type-compute-timer nil) 253 | 254 | (defun dhall-buffer-type-compute (buffer) 255 | "Recompute variable `dhall-buffer-type' in BUFFER." 256 | (with-current-buffer buffer 257 | (let ((type (dhall-buffer-type))) 258 | (setq dhall-buffer-type 259 | (if type 260 | (if (<= (length type) (window-width)) 261 | type 262 | (concat 263 | (substring type 0 264 | (- (window-width) 10)) 265 | "...")) 266 | (propertize "Error determining type. See *dhall-buffer-type-errors*" 'face 'error)))))) 267 | 268 | (defun dhall-after-change (&optional _beg _end _length) 269 | "Called after any change in the buffer." 270 | (when dhall-use-header-line 271 | (when dhall-buffer-type-compute-timer 272 | (cancel-timer dhall-buffer-type-compute-timer)) 273 | (setq dhall-buffer-type-compute-timer 274 | (run-at-time dhall-type-check-inactivity-timeout 275 | nil 276 | (apply-partially 'dhall-buffer-type-compute (current-buffer)))))) 277 | 278 | ;; The main mode functions 279 | ;;;###autoload 280 | (define-derived-mode dhall-mode prog-mode 281 | "Dhall" 282 | "Major mode for editing Dhall files." 283 | :group 'dhall 284 | :keymap dhall-mode-map 285 | :syntax-table dhall-mode-syntax-table 286 | (when dhall-use-header-line 287 | (setq header-line-format 288 | '((:eval dhall-buffer-type))) 289 | (dhall-after-change)) 290 | (setq font-lock-defaults '(dhall-mode-font-lock-keywords)) 291 | (setq-local indent-tabs-mode t) 292 | (setq-local tab-width 4) 293 | (setq-local comment-start "-- ") 294 | (setq-local comment-end "") 295 | ;; Use only spaces for indentation, for consistency with "dhall format" 296 | (setq-local indent-tabs-mode nil) 297 | ;; Special syntax properties for Dhall 298 | (setq-local syntax-propertize-function 'dhall-syntax-propertize) 299 | (add-hook 'after-change-functions 'dhall-after-change nil t) 300 | (when dhall-format-at-save 301 | (dhall-format-on-save-mode))) 302 | 303 | ;; Automatically use dhall-mode for .dhall files. 304 | ;;;###autoload 305 | (add-to-list 'auto-mode-alist '("\\.dhall\\'" . dhall-mode)) 306 | 307 | 308 | ;; REPL 309 | (defconst dhall-prompt-regexp "⊢ ") 310 | 311 | (define-derived-mode dhall-repl-mode comint-mode "Dhall-REPL" 312 | "Interactive prompt for Dhall." 313 | (setq-local comint-prompt-regexp dhall-prompt-regexp) 314 | (setq-local comint-prompt-read-only t)) 315 | 316 | (defun dhall-repl-show () 317 | "Load the Dhall-REPL." 318 | (interactive) 319 | (pop-to-buffer-same-window 320 | (get-buffer-create "*Dhall-REPL*")) 321 | (unless (comint-check-proc (current-buffer)) 322 | (dhall--make-repl-in-buffer (current-buffer)) 323 | (dhall-repl-mode))) 324 | 325 | (defun dhall--make-repl-in-buffer (buffer) 326 | "Make Dhall Repl in BUFFER." 327 | (make-comint-in-buffer "Dhall-REPL" buffer dhall-command nil "repl")) 328 | 329 | (defun dhall-buffer-type-show () 330 | "Show the type of the current buffer's dhall expression in the minibuffer." 331 | (interactive) 332 | (let ((type (dhall-buffer-type))) 333 | (if type 334 | (message type) 335 | (user-error "Error determining type. See *dhall-buffer-type-errors*")))) 336 | 337 | 338 | ;; Provide ourselves: 339 | (provide 'dhall-mode) 340 | 341 | ;; Local Variables: 342 | ;; coding: utf-8 343 | ;; End: 344 | 345 | ;;; dhall-mode.el ends here 346 | --------------------------------------------------------------------------------