400 lines
14 KiB
EmacsLisp
400 lines
14 KiB
EmacsLisp
;; Major mode for editing Michelson smart contracts.
|
|
|
|
(defvar michelson-mode-hook nil)
|
|
|
|
(defgroup michelson nil
|
|
"Major mode for editing Michelson smart contracts."
|
|
:prefix "michelson-"
|
|
:group 'languages)
|
|
|
|
(defgroup michelson-options nil
|
|
"General options for Michelson mode."
|
|
:prefix "michelson-"
|
|
:group 'michelson)
|
|
|
|
(defcustom michelson-mode-client-command "tezos-client"
|
|
"Path to the `tezos-client' binary."
|
|
:type 'string
|
|
:group 'michelson-options)
|
|
|
|
(defgroup michelson-faces nil
|
|
"Font lock faces for Michelson mode."
|
|
:prefix "michelson-"
|
|
:group 'michelson
|
|
:group 'faces)
|
|
|
|
(defvar michelson-face-instruction
|
|
'michelson-face-instruction
|
|
"Face name for Michelson instructions.")
|
|
(defface michelson-face-instruction
|
|
'((t (:inherit 'font-lock-keyword-face)))
|
|
"Face for Michelson instructions."
|
|
:group 'michelson-faces)
|
|
|
|
(defvar michelson-face-type
|
|
'michelson-face-type
|
|
"Face name for Michelson types.")
|
|
(defface michelson-face-type
|
|
'((t (:inherit 'font-lock-type-face)))
|
|
"Face for Michelson types."
|
|
:group 'michelson-faces)
|
|
|
|
(defvar michelson-face-constant
|
|
'michelson-face-constant
|
|
"Face name for Michelson constants.")
|
|
(defface michelson-face-constant
|
|
'((t (:inherit 'font-lock-constant-face)))
|
|
"Face for Michelson constants."
|
|
:group 'michelson-faces)
|
|
|
|
(defvar michelson-face-instruction
|
|
'michelson-face-instruction
|
|
"Face name for Michelson instructions.")
|
|
(defface michelson-face-annotation
|
|
'((t (:inherit 'font-lock-string-face)))
|
|
"Face for Michelson annotations."
|
|
:group 'michelson-faces)
|
|
|
|
(defvar michelson-face-comment
|
|
'michelson-face-comment
|
|
"Face name for Michelson comments.")
|
|
(defface michelson-face-comment
|
|
'((t (:inherit 'font-lock-comment-face)))
|
|
"Face for Michelson comments."
|
|
:group 'michelson-faces)
|
|
|
|
(defvar michelson-face-error
|
|
'michelson-face-error
|
|
"Face name for Michelson comments.")
|
|
(defface michelson-face-error
|
|
'(( ((class color) (background light)) (:background "MistyRose") )
|
|
( ((class color) (background dark)) (:background "DarkRed") ))
|
|
"Face for Michelson annotations."
|
|
:group 'michelson-faces)
|
|
|
|
(defun michelson-customize-options ()
|
|
"Open the general customization group for Michelson mode."
|
|
(interactive)
|
|
(customize-group-other-window `michelson-options))
|
|
|
|
(defun michelson-customize-faces ()
|
|
"Open the face customization group for Michelson mode."
|
|
(interactive)
|
|
(customize-group-other-window `michelson-faces))
|
|
|
|
(defconst michelson-mode-map
|
|
(let ((michelson-mode-map (make-sparse-keymap)))
|
|
;; menu
|
|
(define-key michelson-mode-map
|
|
[menu-bar michelson-menu]
|
|
(cons "Michelson" (make-sparse-keymap "michelson-menu")))
|
|
(define-key michelson-mode-map
|
|
[menu-bar michelson-menu faces]
|
|
(cons "Display options group" 'michelson-customize-faces))
|
|
(define-key michelson-mode-map
|
|
[menu-bar michelson-menu options]
|
|
(cons "General options group" 'michelson-customize-options))
|
|
(define-key michelson-mode-map
|
|
[menu-bar michelson-menu separator]
|
|
'(menu-item "--"))
|
|
(define-key michelson-mode-map
|
|
[menu-bar michelson-menu what]
|
|
(cons "What's under the cursor?" 'michelson-type-at-point))
|
|
;; keys
|
|
(define-key michelson-mode-map
|
|
(kbd "C-j") 'newline-and-indent)
|
|
(define-key michelson-mode-map
|
|
(kbd "TAB") 'indent-for-tab-command)
|
|
(define-key michelson-mode-map
|
|
(kbd "<f1>") 'michelson-type-at-point)
|
|
michelson-mode-map))
|
|
|
|
(defun michelson-font-lock-syntactic-face-function (s)
|
|
(cond ((nth 3 s) 'font-lock-constant-face)
|
|
(t 'michelson-face-comment)))
|
|
|
|
(defconst michelson-font-lock-defaults
|
|
(list
|
|
(list
|
|
'("\\<[0-9]+\\>" . michelson-face-constant)
|
|
'("\\<[A-Z][a-z_0-9]+\\>" . michelson-face-constant)
|
|
'("\\<[A-Z][A-Z_0-9]*\\>" . michelson-face-instruction)
|
|
'("\\<\$[A-Za-z-_0-9]*\\>" . michelson-face-annotation)
|
|
'("\\<[a-z][a-z_0-9]*\\>" . michelson-face-type))
|
|
nil nil nil nil
|
|
'(font-lock-syntactic-face-function . michelson-font-lock-syntactic-face-function)))
|
|
|
|
(defconst michelson-mode-syntax-table
|
|
(let ((michelson-mode-syntax-table (make-syntax-table)))
|
|
(modify-syntax-entry ?_ "w" michelson-mode-syntax-table)
|
|
(modify-syntax-entry ?/ ". 1n4" michelson-mode-syntax-table)
|
|
(modify-syntax-entry ?* ". 23" michelson-mode-syntax-table)
|
|
(modify-syntax-entry ?# "<b" michelson-mode-syntax-table)
|
|
(modify-syntax-entry ?\n ">b" michelson-mode-syntax-table)
|
|
michelson-mode-syntax-table))
|
|
|
|
(defun in-space ()
|
|
(or (looking-at "[[:space:]\n]")
|
|
(equal (get-text-property (point) 'face)
|
|
'michelson-face-comment)))
|
|
|
|
(defun michelson-goto-previous-token ()
|
|
(interactive)
|
|
(if (bobp)
|
|
(cons 0 nil)
|
|
(progn
|
|
(backward-char 1)
|
|
(while (and (not (bobp)) (in-space)) (backward-char 1))
|
|
(let ((token-face (get-text-property (point) 'face)))
|
|
(forward-char 1)
|
|
(let ((end-of-token (point)))
|
|
(backward-char 1)
|
|
(unless (looking-at "[{()};]")
|
|
(while (and (not (bobp))
|
|
(equal (get-text-property (point) 'face) token-face))
|
|
(backward-char 1))
|
|
(when (not (equal (get-text-property (point) 'face) token-face))
|
|
(forward-char 1)))
|
|
(cons (point) (buffer-substring-no-properties (point) end-of-token)))))))
|
|
|
|
(defun michelson-goto-next-token ()
|
|
(interactive)
|
|
(if (eobp)
|
|
(cons (point) nil)
|
|
(progn
|
|
(while (and (not (eobp)) (in-space)) (forward-char 1))
|
|
(let ((token-face (get-text-property (point) 'face)))
|
|
(let ((start-of-token (point)))
|
|
(if (looking-at "[{()};]")
|
|
(forward-char 1)
|
|
(progn
|
|
(while (and (not (eobp))
|
|
(equal (get-text-property (point) 'face) token-face))
|
|
(forward-char 1))))
|
|
(cons start-of-token (buffer-substring-no-properties start-of-token (point))))))))
|
|
|
|
(defun michelson-goto-opener ()
|
|
(interactive)
|
|
(let ((paren-level 0))
|
|
(while (and (not (bobp))
|
|
(or (> paren-level 0)
|
|
(not (looking-at "[{(]"))))
|
|
(cond ((looking-at "[{(]")
|
|
(setq paren-level (- paren-level 1)))
|
|
((looking-at "[})]")
|
|
(setq paren-level (+ paren-level 1))))
|
|
(michelson-goto-previous-token))
|
|
(cons (point)
|
|
(when (looking-at "[{(]")
|
|
(buffer-substring-no-properties (point) (+ (point) 1))))))
|
|
|
|
(defun michelson-goto-closer ()
|
|
(interactive)
|
|
(let ((paren-level 0) (last-token ""))
|
|
(while (and (not (eobp))
|
|
(or (> paren-level 0)
|
|
(not (string-match "[)}]" last-token))))
|
|
(cond ((looking-at "[{(]")
|
|
(setq paren-level (+ paren-level 1)))
|
|
((looking-at "[})]")
|
|
(setq paren-level (- paren-level 1))))
|
|
(setq last-token (cdr (michelson-goto-next-token))))
|
|
(cons (point)
|
|
(when (looking-at "[)}]")
|
|
(buffer-substring-no-properties (point) (+ (point) 1))))))
|
|
|
|
(defun michelson-goto-previous-application-start ()
|
|
(interactive)
|
|
(let ((paren-level 0) (application-start 0))
|
|
(while (and (not (bobp))
|
|
(or (> paren-level 0)
|
|
(not (looking-at "[{(;]"))))
|
|
(cond ((looking-at "[{(]")
|
|
(setq paren-level (- paren-level 1)))
|
|
((looking-at "[})]")
|
|
(setq paren-level (+ paren-level 1))))
|
|
(setq application-start (point))
|
|
(michelson-goto-previous-token))
|
|
(cons application-start (goto-char application-start))))
|
|
|
|
(defun michelson-indent ()
|
|
"Indent current line of Michelson code."
|
|
(interactive)
|
|
(let ((new-indentation 0)
|
|
(previous-indentation (current-indentation))
|
|
(previous-column (current-column))
|
|
(current-token
|
|
(save-excursion
|
|
(beginning-of-line 1)
|
|
(michelson-goto-next-token))))
|
|
(save-excursion
|
|
(end-of-line 0)
|
|
(let ((previous-token
|
|
(save-excursion (michelson-goto-previous-token)))
|
|
(previous-opener
|
|
(save-excursion (michelson-goto-opener))))
|
|
(cond ((and (not (cdr previous-opener))
|
|
(not (cdr previous-token)))
|
|
(setq new-indentation 0))
|
|
((and (not (cdr previous-opener))
|
|
(equal (cdr previous-token) ";"))
|
|
(setq new-indentation 0))
|
|
((not (cdr previous-opener))
|
|
(setq new-indentation 2))
|
|
((and (equal (cdr current-token) "}")
|
|
(equal (cdr previous-opener) "{"))
|
|
(goto-char (car previous-opener))
|
|
(setq new-indentation (current-column)))
|
|
((and (or (equal (cdr previous-token) ";")
|
|
(equal (cdr previous-token) "{"))
|
|
(equal (cdr previous-opener) "{"))
|
|
(goto-char (car previous-opener))
|
|
(setq new-indentation (+ (current-column) 2)))
|
|
((equal (cdr previous-opener) "{")
|
|
(progn
|
|
(michelson-goto-previous-application-start)
|
|
(let ((default-param-indentation
|
|
(+ (current-column) 2))
|
|
(first-param-point
|
|
(save-excursion
|
|
(michelson-goto-next-token)
|
|
(car (michelson-goto-next-token)))))
|
|
(if (= first-param-point (car current-token))
|
|
(setq new-indentation default-param-indentation)
|
|
(progn
|
|
(goto-char first-param-point)
|
|
(setq new-indentation (current-column)))))))
|
|
((and (equal (cdr current-token) ")")
|
|
(equal (cdr previous-opener) "("))
|
|
(goto-char (car previous-opener))
|
|
(setq new-indentation (current-column)))
|
|
((equal (cdr previous-token) "(")
|
|
(goto-char (car previous-token))
|
|
(setq new-indentation (+ (current-column) 1)))
|
|
((equal (cdr previous-opener) "(")
|
|
(goto-char (car previous-opener))
|
|
(setq new-indentation (+ (current-column) 3))))))
|
|
(indent-line-to new-indentation)
|
|
(beginning-of-line)
|
|
(forward-char
|
|
(+ (- previous-column previous-indentation) new-indentation))
|
|
(when (< (current-column) new-indentation)
|
|
(beginning-of-line)
|
|
(forward-char new-indentation))))
|
|
|
|
(defun michelson-token-at-point ()
|
|
"Display the token closest to the cursor."
|
|
(interactive)
|
|
(let ((message
|
|
(cdr (save-excursion
|
|
(michelson-goto-next-token)
|
|
(michelson-goto-previous-token)))))
|
|
(display-message-or-buffer message "*Michelson*")))
|
|
|
|
(defun michelson-type-at-point ()
|
|
"Display the type of the expression under the cursor."
|
|
(interactive)
|
|
(let ((tmp-file (concat buffer-file-name ".emacs")))
|
|
(write-region (point-min) (point-max) tmp-file nil 'no-message)
|
|
(let* ((stdout
|
|
(shell-command-to-string
|
|
(concat
|
|
michelson-mode-client-command
|
|
" typecheck program "
|
|
tmp-file
|
|
" -details -emacs")))
|
|
(record
|
|
(car (read-from-string stdout)))
|
|
(errors
|
|
(cdr (assoc 'errors record)))
|
|
(types
|
|
(cdr (assoc 'types record)))
|
|
(message ""))
|
|
(delete-file tmp-file)
|
|
(remove-overlays)
|
|
(setq message "")
|
|
(dolist (elt types)
|
|
(if (and (<= (car elt) (point)) (<= (point) (cadr elt))
|
|
(equal (get-text-property (point) 'face)
|
|
'michelson-face-instruction))
|
|
(setq message (cadr (cdr elt)))))
|
|
(dolist (elt errors)
|
|
(overlay-put (make-overlay (car elt) (cadr elt)) 'face 'michelson-face-error)
|
|
(if (and (<= (car elt) (point)) (<= (point) (cadr elt)))
|
|
(progn
|
|
(if (string= "" message)
|
|
nil
|
|
(setq message (concat message "\n")))
|
|
(setq message (concat message (cadr (cdr elt)))))))
|
|
(if (string= message "")
|
|
(setq message "\n No instruction at point."))
|
|
(let
|
|
((message-window
|
|
(display-buffer-below-selected
|
|
(get-buffer-create "*Michelson*") nil))
|
|
(lines 0))
|
|
(save-excursion
|
|
(set-buffer "*Michelson*")
|
|
(toggle-read-only nil)
|
|
(erase-buffer)
|
|
(insert message)
|
|
(toggle-read-only t)
|
|
(beginning-of-buffer)
|
|
(while (not (eobp))
|
|
(vertical-motion 1)
|
|
(setq lines (+ 1 lines)))
|
|
(window-resize
|
|
message-window
|
|
(+ (- (max 4 lines)
|
|
(window-size message-window)) 1)))))))
|
|
|
|
(defun michelson-update-minibuffer-info ()
|
|
(when (nth 2 michelson-state) (cancel-timer (nth 2 michelson-state)))
|
|
(setf
|
|
(nth 2 michelson-state)
|
|
(run-at-time
|
|
"0.3 sec" nil
|
|
(lambda (buffer)
|
|
(with-current-buffer buffer
|
|
(setf (nth 2 michelson-state) nil)
|
|
(when (not (= (nth 0 michelson-state) (point)))
|
|
(setf (nth 0 michelson-state) (point))
|
|
(michelson-type-at-point))))
|
|
(current-buffer))))
|
|
|
|
(define-derived-mode michelson-mode prog-mode "Michelson"
|
|
"Major mode for editing Michelson smart contracts."
|
|
(interactive)
|
|
(kill-all-local-variables)
|
|
(use-local-map michelson-mode-map)
|
|
(set-syntax-table michelson-mode-syntax-table)
|
|
(set
|
|
(make-local-variable 'font-lock-defaults)
|
|
michelson-font-lock-defaults)
|
|
(set
|
|
(make-local-variable 'indent-line-function)
|
|
'michelson-indent)
|
|
(set
|
|
(make-local-variable 'indent-for-tab-command)
|
|
'michelson-indent)
|
|
(set
|
|
(make-local-variable 'michelson-state)
|
|
(list 0 0 nil))
|
|
(add-to-list
|
|
(make-local-variable 'pre-command-hook)
|
|
'michelson-update-minibuffer-info)
|
|
(add-to-list
|
|
(make-local-variable 'focus-in-hook)
|
|
'michelson-update-minibuffer-info)
|
|
(setq major-mode 'michelson-mode)
|
|
(setq mode-name "Michelson")
|
|
(setq indent-tabs-mode nil)
|
|
(setq show-trailing-whitespace t)
|
|
(setq buffer-file-coding-system 'utf-8-unix)
|
|
(run-hooks 'michelson-mode-hook))
|
|
|
|
(add-to-list 'auto-mode-alist '("\\.tz\\'" . michelson-mode))
|
|
(add-to-list 'auto-mode-alist '("\\.tez\\'" . michelson-mode))
|
|
|
|
(provide 'michelson-mode)
|