Michelson-mode: async, cache, and refactorings
This commit is contained in:
parent
3255fc8cd8
commit
953a319022
56
emacs/README.md
Normal file
56
emacs/README.md
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# Michelson Emacs mode
|
||||||
|
This mode is a work in progress.
|
||||||
|
Please contact us with bugs and feature requests.
|
||||||
|
All of the options mentioned below are also accessible via the customize menu.
|
||||||
|
|
||||||
|
## Required Configuration
|
||||||
|
To use the mode, you must load the `michelson-mode.el` file into Emacs.
|
||||||
|
Add the following to your `.emacs` file.
|
||||||
|
```elisp
|
||||||
|
(load "~/tezos/tezos/emacs/michelson-mode.el" nil t)
|
||||||
|
```
|
||||||
|
|
||||||
|
Before using the Emacs mode, you must configure the `michelson-client-command`.
|
||||||
|
If you have compiled the Tezos Git repository,
|
||||||
|
set this to be the path to the `tezos-client` binary on your system.
|
||||||
|
Make sure you have an up to date version of the client compiled.
|
||||||
|
You must also start a tezos node to enable typechecking features.
|
||||||
|
This option is recommended because it is faster than operating through
|
||||||
|
the docker container.
|
||||||
|
|
||||||
|
If you wish to run the Emacs mode with the alphanet script,
|
||||||
|
use the path of the `alphanet.sh` script, plus the word `client`.
|
||||||
|
You must also set the `michelson-alphanet` variable to be `t`.
|
||||||
|
If you do not set this option, the mode will not work with the alphanet.
|
||||||
|
|
||||||
|
Here are examples of the client configuration:
|
||||||
|
### Without the alphanet
|
||||||
|
```elisp
|
||||||
|
(setq michelson-client-command "~/tezos/tezos/tezos-client")
|
||||||
|
(setq michelson-alphanet nil)
|
||||||
|
```
|
||||||
|
### With the alphanet
|
||||||
|
```elisp
|
||||||
|
(setq michelson-client-command "~/tezos/alphanet/alphanet.sh client")
|
||||||
|
(setq michelson-alphanet t)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Additional Options
|
||||||
|
There are various feature of the Emacs mode you may wish to configure.
|
||||||
|
|
||||||
|
### Error display
|
||||||
|
When writing a contract, you may wish to disable error display in order to
|
||||||
|
avoid the "wrong stack type at end of body" error that is often present.
|
||||||
|
This can be done by changing the
|
||||||
|
`michelson-print-errors` and `michelson-highlight-errors` options.
|
||||||
|
Both of these options also have interactive toggles for easy access.
|
||||||
|
|
||||||
|
### Live printing
|
||||||
|
You can disable live printing using the `michelson-live-editing` option.
|
||||||
|
If this option is disabled, both type and error printing are supressed.
|
||||||
|
No background command will be run, limiting the mode to syntax highlighting.
|
||||||
|
This command can also be toggled interactively using the
|
||||||
|
`michelson-toggle-live-editing` command.
|
||||||
|
|
||||||
|
### Faces
|
||||||
|
The highlighting colors used can be configured. See the customize menu for details.
|
@ -1,5 +1,7 @@
|
|||||||
;; Major mode for editing Michelson smart contracts.
|
;; Major mode for editing Michelson smart contracts.
|
||||||
|
|
||||||
|
(require 'cl)
|
||||||
|
|
||||||
(defvar michelson-mode-hook nil)
|
(defvar michelson-mode-hook nil)
|
||||||
|
|
||||||
(defgroup michelson nil
|
(defgroup michelson nil
|
||||||
@ -12,7 +14,7 @@
|
|||||||
:prefix "michelson-"
|
:prefix "michelson-"
|
||||||
:group 'michelson)
|
:group 'michelson)
|
||||||
|
|
||||||
(defcustom michelson-mode-client-command "tezos-client"
|
(defcustom michelson-client-command "tezos-client"
|
||||||
"Path to the `tezos-client' binary."
|
"Path to the `tezos-client' binary."
|
||||||
:type 'string
|
:type 'string
|
||||||
:group 'michelson-options)
|
:group 'michelson-options)
|
||||||
@ -28,6 +30,21 @@
|
|||||||
:group 'michelson
|
:group 'michelson
|
||||||
:group 'faces)
|
:group 'faces)
|
||||||
|
|
||||||
|
(defcustom michelson-live-editing t
|
||||||
|
"Toggles live types and error printing.
|
||||||
|
Overrides `michelson-print-errors' and `michelson-highlight-errors'"
|
||||||
|
:group 'michelson-options)
|
||||||
|
|
||||||
|
(defcustom michelson-print-errors t
|
||||||
|
"Print the errors in the output buffer."
|
||||||
|
:type 'boolean
|
||||||
|
:group 'michelson-options)
|
||||||
|
|
||||||
|
(defcustom michelson-highlight-errors t
|
||||||
|
"Highlight errors in the source buffer."
|
||||||
|
:type 'boolean
|
||||||
|
:group 'michelson-options)
|
||||||
|
|
||||||
(defvar michelson-face-instruction
|
(defvar michelson-face-instruction
|
||||||
'michelson-face-instruction
|
'michelson-face-instruction
|
||||||
"Face name for Michelson instructions.")
|
"Face name for Michelson instructions.")
|
||||||
@ -47,6 +64,7 @@
|
|||||||
(defvar michelson-face-constant
|
(defvar michelson-face-constant
|
||||||
'michelson-face-constant
|
'michelson-face-constant
|
||||||
"Face name for Michelson constants.")
|
"Face name for Michelson constants.")
|
||||||
|
|
||||||
(defface michelson-face-constant
|
(defface michelson-face-constant
|
||||||
'((t (:inherit 'font-lock-constant-face)))
|
'((t (:inherit 'font-lock-constant-face)))
|
||||||
"Face for Michelson constants."
|
"Face for Michelson constants."
|
||||||
@ -68,9 +86,19 @@
|
|||||||
"Face for Michelson comments."
|
"Face for Michelson comments."
|
||||||
:group 'michelson-faces)
|
:group 'michelson-faces)
|
||||||
|
|
||||||
|
(defvar michelson-face-declaration
|
||||||
|
'michelson-face-declaration
|
||||||
|
"Face name for Michelson declarations.")
|
||||||
|
|
||||||
|
(defface michelson-face-declaration
|
||||||
|
'((t (:inherit 'font-lock-keyword-face)))
|
||||||
|
"Face for Michelson constants."
|
||||||
|
:group 'michelson-faces)
|
||||||
|
|
||||||
(defvar michelson-face-error
|
(defvar michelson-face-error
|
||||||
'michelson-face-error
|
'michelson-face-error
|
||||||
"Face name for Michelson comments.")
|
"Face name for Michelson comments.")
|
||||||
|
|
||||||
(defface michelson-face-error
|
(defface michelson-face-error
|
||||||
'(( ((class color) (background light)) (:background "MistyRose") )
|
'(( ((class color) (background light)) (:background "MistyRose") )
|
||||||
( ((class color) (background dark)) (:background "DarkRed") ))
|
( ((class color) (background dark)) (:background "DarkRed") ))
|
||||||
@ -87,6 +115,14 @@
|
|||||||
(interactive)
|
(interactive)
|
||||||
(customize-group-other-window `michelson-faces))
|
(customize-group-other-window `michelson-faces))
|
||||||
|
|
||||||
|
(defun michelson-toggle-print-errors ()
|
||||||
|
(interactive)
|
||||||
|
(setq michelson-print-errors (not michelson-print-errors)))
|
||||||
|
|
||||||
|
(defun michelson-highlight-errors ()
|
||||||
|
(interactive)
|
||||||
|
(setq michelson-highlight-errors (not michelson-highlight-errors)))
|
||||||
|
|
||||||
(defconst michelson-mode-map
|
(defconst michelson-mode-map
|
||||||
(let ((michelson-mode-map (make-sparse-keymap)))
|
(let ((michelson-mode-map (make-sparse-keymap)))
|
||||||
;; menu
|
;; menu
|
||||||
@ -125,6 +161,8 @@
|
|||||||
'("\\<[A-Z][a-z_0-9]+\\>" . michelson-face-constant)
|
'("\\<[A-Z][a-z_0-9]+\\>" . michelson-face-constant)
|
||||||
'("\\<[A-Z][A-Z_0-9]*\\>" . michelson-face-instruction)
|
'("\\<[A-Z][A-Z_0-9]*\\>" . michelson-face-instruction)
|
||||||
'("\\<\$[A-Za-z-_0-9]*\\>" . michelson-face-annotation)
|
'("\\<\$[A-Za-z-_0-9]*\\>" . michelson-face-annotation)
|
||||||
|
;; This will have problems if users have whitespace in front of the declarations
|
||||||
|
'("^parameter\\|^return\\|^storage\\|^code" . michelson-face-declaration)
|
||||||
'("\\<[a-z][a-z_0-9]*\\>" . michelson-face-type))
|
'("\\<[a-z][a-z_0-9]*\\>" . michelson-face-type))
|
||||||
nil nil nil nil
|
nil nil nil nil
|
||||||
'(font-lock-syntactic-face-function . michelson-font-lock-syntactic-face-function)))
|
'(font-lock-syntactic-face-function . michelson-font-lock-syntactic-face-function)))
|
||||||
@ -296,68 +334,157 @@
|
|||||||
(michelson-goto-previous-token)))))
|
(michelson-goto-previous-token)))))
|
||||||
(display-message-or-buffer message "*Michelson*")))
|
(display-message-or-buffer message "*Michelson*")))
|
||||||
|
|
||||||
(defun michelson-type-at-point ()
|
(cl-defstruct cache
|
||||||
"Display the type of the expression under the cursor."
|
"Cache for types. Invalid entries are removed"
|
||||||
(interactive)
|
types
|
||||||
(let ((tmp-file (make-temp-file (buffer-name))))
|
errors)
|
||||||
|
|
||||||
|
(defvar michelson-cached-buffer-info (make-cache :types '() :errors '()))
|
||||||
|
|
||||||
|
(defvar michelson-process-output-buffer "*Michelson-process*")
|
||||||
|
|
||||||
|
(defun michelson-erase-process-buffer ()
|
||||||
|
"Remove all text from process buffer."
|
||||||
|
(get-buffer-create michelson-process-output-buffer)
|
||||||
|
(with-current-buffer michelson-process-output-buffer
|
||||||
|
(erase-buffer)))
|
||||||
|
|
||||||
|
(defun michelson-async-command-to-string (command callback)
|
||||||
|
"Asynchronously execute `COMMAND' and call the `CALLBACK' on the resulting string."
|
||||||
|
(lexical-let ((callback-fun callback))
|
||||||
|
(michelson-erase-process-buffer)
|
||||||
|
(set-process-sentinel
|
||||||
|
(start-process-shell-command
|
||||||
|
"michelson-shell-process"
|
||||||
|
michelson-process-output-buffer
|
||||||
|
command)
|
||||||
|
(lambda (process signal)
|
||||||
|
(let ((output
|
||||||
|
(with-current-buffer michelson-process-output-buffer
|
||||||
|
(buffer-string))))
|
||||||
|
(funcall callback-fun output))))))
|
||||||
|
|
||||||
|
(defun michelson-clean-cache ()
|
||||||
|
"Clean the buffer's program info cache."
|
||||||
|
(let ((types (cache-types michelson-cached-buffer-info))
|
||||||
|
(errors (cache-errors michelson-cached-buffer-info))
|
||||||
|
(clean-cache-entry
|
||||||
|
(lambda (alist)
|
||||||
|
(remove-if (lambda (entry)
|
||||||
|
(let ((tok-end (cadr entry)))
|
||||||
|
(> tok-end (point))))
|
||||||
|
alist))))
|
||||||
|
(setq michelson-cached-buffer-info
|
||||||
|
(make-cache :types (funcall clean-cache-entry types)
|
||||||
|
:errors (funcall clean-cache-entry errors)))))
|
||||||
|
|
||||||
|
(defun michelson-get-info (buffer-name)
|
||||||
|
"Refresh the info about the program in `BUFFER-NAME' from the command."
|
||||||
|
(lexical-let ((tmp-file (make-temp-file buffer-name)))
|
||||||
(write-region (point-min) (point-max) tmp-file nil 'no-message)
|
(write-region (point-min) (point-max) tmp-file nil 'no-message)
|
||||||
(let* ((command (concat
|
(let ((command (concat
|
||||||
(if michelson-alphanet "ALPHANET_EMACS=true " "")
|
"ALPHANET_EMACS=true "
|
||||||
michelson-mode-client-command
|
"TEZOS_ALPHANET_DO_NOT_PULL=yes "
|
||||||
" typecheck program "
|
michelson-client-command
|
||||||
(if michelson-alphanet
|
" typecheck program "
|
||||||
(concat "container:" buffer-file-name)
|
(let ((file-name
|
||||||
buffer-file-name)
|
(make-temp-file (buffer-name))))
|
||||||
" -details -emacs"))
|
(write-region (point-min) (point-max) file-name nil 'no-message)
|
||||||
(stdout
|
(if michelson-alphanet
|
||||||
(shell-command-to-string command))
|
(concat "container:" file-name)
|
||||||
(record
|
file-name))
|
||||||
(car (read-from-string stdout)))
|
" -details -emacs")))
|
||||||
(errors
|
(michelson-async-command-to-string
|
||||||
(cdr (assoc 'errors record)))
|
command
|
||||||
(types
|
(lambda (output)
|
||||||
(cdr (assoc 'types record)))
|
(condition-case err
|
||||||
(message ""))
|
(let*
|
||||||
(delete-file tmp-file)
|
((record (car (read-from-string output)))
|
||||||
(remove-overlays)
|
(errors (cdr (assoc 'errors record)))
|
||||||
(setq message "")
|
(types (cdr (assoc 'types record))))
|
||||||
(dolist (elt types)
|
(setq michelson-cached-buffer-info (make-cache :types types :errors errors)))
|
||||||
(if (and (<= (car elt) (point)) (<= (point) (cadr elt))
|
((error err)
|
||||||
(equal (get-text-property (point) 'face)
|
(let ((inhibit-message t))
|
||||||
'michelson-face-instruction))
|
(message output)))))))))
|
||||||
(setq message (cadr (cdr elt)))))
|
|
||||||
|
(defvar michelson-output-buffer-name
|
||||||
|
"*Michelson*")
|
||||||
|
|
||||||
|
(defun michelson-write-output-buffer (data)
|
||||||
|
"Write the given `DATA' to the output buffer."
|
||||||
|
(lexical-let*
|
||||||
|
((buffer (get-buffer-create michelson-output-buffer-name))
|
||||||
|
(message-window
|
||||||
|
(if (get-buffer-window buffer)
|
||||||
|
(get-buffer-window buffer)
|
||||||
|
(display-buffer-below-selected buffer nil)))
|
||||||
|
(lines 0))
|
||||||
|
(when (get-buffer-window buffer)
|
||||||
|
(set-window-dedicated-p (get-buffer-window buffer) t))
|
||||||
|
(save-excursion
|
||||||
|
(set-buffer michelson-output-buffer-name)
|
||||||
|
(read-only-mode -1)
|
||||||
|
(erase-buffer)
|
||||||
|
(insert data)
|
||||||
|
(read-only-mode 1)
|
||||||
|
(goto-char (point-min))
|
||||||
|
(while (not (eobp))
|
||||||
|
(vertical-motion 1)
|
||||||
|
(setq lines (+ 1 lines)))
|
||||||
|
(window-resize
|
||||||
|
message-window
|
||||||
|
(min (- (window-total-height) 5)
|
||||||
|
(+ (- (max 4 lines)
|
||||||
|
(window-size message-window))
|
||||||
|
2))))))
|
||||||
|
|
||||||
|
(defun michelson-show-program-info (types errors)
|
||||||
|
"Show the program's `TYPES' and `ERRORS'."
|
||||||
|
(interactive)
|
||||||
|
(remove-overlays)
|
||||||
|
(lexical-let ((types-info nil)
|
||||||
|
(errors-info nil))
|
||||||
|
(dolist (elt types)
|
||||||
|
(if (and (<= (car elt) (point)) (<= (point) (cadr elt))
|
||||||
|
(equal (get-text-property (point) 'face)
|
||||||
|
'michelson-face-instruction))
|
||||||
|
(setq types-info (cadr (cdr elt)))))
|
||||||
|
(when michelson-highlight-errors
|
||||||
(dolist (elt errors)
|
(dolist (elt errors)
|
||||||
(overlay-put (make-overlay (car elt) (cadr elt)) 'face 'michelson-face-error)
|
(overlay-put (make-overlay (car elt) (cadr elt)) 'face 'michelson-face-error)
|
||||||
(if (and (<= (car elt) (point)) (<= (point) (cadr elt)))
|
(if (and (<= (car elt) (point)) (<= (point) (cadr elt)))
|
||||||
(progn
|
(progn
|
||||||
(if (string= "" message)
|
(when michelson-print-errors
|
||||||
nil
|
(unless errors-info
|
||||||
(setq message (concat message "\n")))
|
(setq errors-info (concat errors-info "\n")))
|
||||||
(setq message (concat message (cadr (cdr elt)))))))
|
(setq errors-info (concat errors-info (cadr (cdr elt)))))))))
|
||||||
(if (string= message "")
|
(michelson-write-output-buffer
|
||||||
(setq message "\n No instruction at point."))
|
(cond ((not (or types-info types-info)) "\n No instruction at point.\n")
|
||||||
(let
|
((and errors-info (not types-info)) errors-info)
|
||||||
((message-window
|
(t (concat types-info "\n\n" errors-info))))))
|
||||||
(display-buffer-below-selected
|
|
||||||
(get-buffer-create "*Michelson*") nil))
|
(defun michelson-type-at-point ()
|
||||||
(lines 0))
|
"Display the type of the expression under the cursor."
|
||||||
(save-excursion
|
(interactive)
|
||||||
(set-buffer "*Michelson*")
|
(michelson-get-info (buffer-name))
|
||||||
(toggle-read-only nil)
|
(let ((types (cache-types michelson-cached-buffer-info))
|
||||||
(erase-buffer)
|
(errors (cache-errors michelson-cached-buffer-info)))
|
||||||
(insert message)
|
(michelson-show-program-info types errors)))
|
||||||
(toggle-read-only t)
|
|
||||||
(beginning-of-buffer)
|
(defun michelson-toggle-live-editing ()
|
||||||
(while (not (eobp))
|
"Toggle `michelson-live-editing'.
|
||||||
(vertical-motion 1)
|
Enables or disables stack and error display."
|
||||||
(setq lines (+ 1 lines)))
|
(interactive)
|
||||||
(window-resize
|
(when (and michelson-live-editing
|
||||||
message-window
|
(get-buffer michelson-output-buffer-name))
|
||||||
(+ (- (max 4 lines)
|
(save-excursion
|
||||||
(window-size message-window)) 1)))))))
|
(set-buffer michelson-output-buffer-name)
|
||||||
|
(kill-buffer-and-window)))
|
||||||
|
(setq michelson-live-editing (not michelson-live-editing)))
|
||||||
|
|
||||||
(defun michelson-update-minibuffer-info ()
|
(defun michelson-update-minibuffer-info ()
|
||||||
(when (nth 2 michelson-state) (cancel-timer (nth 2 michelson-state)))
|
(when (nth 2 michelson-state)
|
||||||
|
(cancel-timer (nth 2 michelson-state)))
|
||||||
(setf
|
(setf
|
||||||
(nth 2 michelson-state)
|
(nth 2 michelson-state)
|
||||||
(run-at-time
|
(run-at-time
|
||||||
@ -365,7 +492,8 @@
|
|||||||
(lambda (buffer)
|
(lambda (buffer)
|
||||||
(with-current-buffer buffer
|
(with-current-buffer buffer
|
||||||
(setf (nth 2 michelson-state) nil)
|
(setf (nth 2 michelson-state) nil)
|
||||||
(when (not (= (nth 0 michelson-state) (point)))
|
(when (and (not (= (nth 0 michelson-state) (point)))
|
||||||
|
michelson-live-editing)
|
||||||
(setf (nth 0 michelson-state) (point))
|
(setf (nth 0 michelson-state) (point))
|
||||||
(michelson-type-at-point))))
|
(michelson-type-at-point))))
|
||||||
(current-buffer))))
|
(current-buffer))))
|
||||||
@ -388,12 +516,16 @@
|
|||||||
(set
|
(set
|
||||||
(make-local-variable 'michelson-state)
|
(make-local-variable 'michelson-state)
|
||||||
(list 0 0 nil))
|
(list 0 0 nil))
|
||||||
|
(set (make-local-variable 'michelson-cached-buffer-info)
|
||||||
|
(make-cache :types nil
|
||||||
|
:errors nil))
|
||||||
(add-to-list
|
(add-to-list
|
||||||
(make-local-variable 'pre-command-hook)
|
(make-local-variable 'pre-command-hook)
|
||||||
'michelson-update-minibuffer-info)
|
'michelson-update-minibuffer-info)
|
||||||
(add-to-list
|
(add-to-list
|
||||||
(make-local-variable 'focus-in-hook)
|
(make-local-variable 'focus-in-hook)
|
||||||
'michelson-update-minibuffer-info)
|
'michelson-update-minibuffer-info)
|
||||||
|
(add-hook 'post-self-insert-hook 'michelson-clean-cache)
|
||||||
(setq major-mode 'michelson-mode)
|
(setq major-mode 'michelson-mode)
|
||||||
(setq mode-name "Michelson")
|
(setq mode-name "Michelson")
|
||||||
(setq indent-tabs-mode nil)
|
(setq indent-tabs-mode nil)
|
||||||
|
Loading…
Reference in New Issue
Block a user