578ed1ba98
Moving all of my Emacs-related files into their own directory at the root of this repository.
216 lines
8.6 KiB
EmacsLisp
216 lines
8.6 KiB
EmacsLisp
;;; reason-interaction.el --- Phrase navitagion for rtop -*-lexical-binding: t-*-
|
|
|
|
;; Portions Copyright (c) 2015-present, Facebook, Inc. All rights reserved.
|
|
|
|
;;; Commentary:
|
|
|
|
;; Phrase navigation for utop and maybe other REPLs.
|
|
|
|
;; The utop compatibility layer for Reason was mainly taken from:
|
|
;; https://github.com/ocaml/tuareg/blob/master/tuareg-light.el (big thanks!)
|
|
|
|
;;; Code:
|
|
|
|
(defun reason-backward-char (&optional step)
|
|
"Go back one char.
|
|
Similar to `backward-char` but it does not signal errors
|
|
`beginning-of-buffer` and `end-of-buffer`. It optionally takes a
|
|
STEP parameter for jumping back more than one character."
|
|
(when step (goto-char (- (point) step))
|
|
(goto-char (1- (point)))))
|
|
|
|
(defun reason-forward-char (&optional step)
|
|
"Go forward one char.
|
|
Similar to `forward-char` but it does not signal errors
|
|
`beginning-of-buffer` and `end-of-buffer`. It optionally takes a
|
|
STEP parameter for jumping back more than one character."
|
|
(when step (goto-char (+ (point) step))
|
|
(goto-char (1+ (point)))))
|
|
|
|
(defun reason-in-literal-p ()
|
|
"Return non-nil if point is inside an Reason literal."
|
|
(nth 3 (syntax-ppss)))
|
|
|
|
(defconst reason-comment-delimiter-regexp "\\*/\\|/\\*"
|
|
"Regex for identify either open or close comment delimiters.")
|
|
|
|
(defun reason-in-between-comment-chars-p ()
|
|
"Return non-nil iff point is in between the comment delimiter chars.
|
|
It returns non-nil if point is between the chars only (*|/ or /|*
|
|
where | is point)."
|
|
(and (not (bobp)) (not (eobp))
|
|
(or (and (char-equal ?/ (char-before)) (char-equal ?* (char-after)))
|
|
(and (char-equal ?* (char-before)) (char-equal ?/ (char-after))))))
|
|
|
|
(defun reason-looking-at-comment-delimiters-p ()
|
|
"Return non-nil iff point in between comment delimiters."
|
|
(looking-at-p reason-comment-delimiter-regexp))
|
|
|
|
(defun reason-in-between-comment-delimiters-p ()
|
|
"Return non-nil if inside /* and */."
|
|
(nth 4 (syntax-ppss)))
|
|
|
|
(defun reason-in-comment-p ()
|
|
"Return non-nil iff point is inside or right before a comment."
|
|
(or (reason-in-between-comment-delimiters-p)
|
|
(reason-in-between-comment-chars-p)
|
|
(reason-looking-at-comment-delimiters-p)))
|
|
|
|
(defun reason-beginning-of-literal-or-comment ()
|
|
"Skip to the beginning of the current literal or comment (or buffer)."
|
|
(interactive)
|
|
(goto-char (or (nth 8 (syntax-ppss)) (point))))
|
|
|
|
(defun reason-inside-block-scope-p ()
|
|
"Skip to the beginning of the current literal or comment (or buffer)."
|
|
(and (> (nth 0 (syntax-ppss)) 0)
|
|
(let ((delim-start (nth 1 (syntax-ppss))))
|
|
(save-excursion
|
|
(goto-char delim-start)
|
|
(char-equal ?{ (following-char))))))
|
|
|
|
(defun reason-at-phrase-break-p ()
|
|
"Is the underlying `;' a phrase break?"
|
|
;; Difference from OCaml, the phrase separator is a single semi-colon
|
|
(and (not (eobp))
|
|
(char-equal ?\; (following-char))))
|
|
|
|
(defun reason-skip-to-close-delimiter (&optional limit)
|
|
"Skip to the end of a Reason block.
|
|
It basically calls `re-search-forward` in order to go to any
|
|
closing delimiter, not concerning itself with balancing of any
|
|
sort. Client code needs to check that.
|
|
LIMIT is passed to `re-search-forward` directly."
|
|
(re-search-forward "\\s)" limit 'move))
|
|
|
|
(defun reason-skip-back-to-open-delimiter (&optional limit)
|
|
"Skip to the beginning of a Reason block backwards.
|
|
It basically calls `re-search-backward` in order to go to any
|
|
opening delimiter, not concerning itself with balancing of any
|
|
sort. Client code needs to check that.
|
|
LIMIT is passed to `re-search-backward` directly."
|
|
(re-search-backward "\\s(" limit 'move))
|
|
|
|
(defun reason-find-phrase-end ()
|
|
"Skip to the end of a phrase."
|
|
(while (and (not (eobp))
|
|
(not (reason-at-phrase-break-p)))
|
|
(if (re-search-forward ";" nil 'move)
|
|
(progn (when (reason-inside-block-scope-p)
|
|
(reason-skip-to-close-delimiter))
|
|
(goto-char (1- (point))))
|
|
;; avoid infinite loop at the end of the buffer
|
|
(re-search-forward "[[:space:]\\|\n]+" nil 'move)))
|
|
(min (goto-char (1+ (point))) (point-max)))
|
|
|
|
(defun reason-skip-blank-and-comments ()
|
|
"Skip blank spaces and comments."
|
|
(cond
|
|
((eobp) (point))
|
|
((or (reason-in-between-comment-chars-p)
|
|
(reason-looking-at-comment-delimiters-p)) (progn
|
|
(reason-forward-char 1)
|
|
(reason-skip-blank-and-comments)))
|
|
((reason-in-between-comment-delimiters-p) (progn
|
|
(search-forward "*/" nil t)
|
|
(reason-skip-blank-and-comments)))
|
|
((eolp) (progn
|
|
(reason-forward-char 1)
|
|
(reason-skip-blank-and-comments)))
|
|
(t (progn (skip-syntax-forward " ")
|
|
(point)))))
|
|
|
|
(defun reason-skip-back-blank-and-comments ()
|
|
"Skip blank spaces and comments backwards."
|
|
(cond
|
|
((bobp) (point))
|
|
((looking-back reason-comment-delimiter-regexp) (progn
|
|
(reason-backward-char 1)
|
|
(reason-skip-back-blank-and-comments)))
|
|
((reason-in-between-comment-delimiters-p) (progn
|
|
(search-backward "/*" nil t)
|
|
(reason-backward-char 1)
|
|
(reason-skip-back-blank-and-comments)))
|
|
((or (reason-in-between-comment-chars-p)
|
|
(reason-looking-at-comment-delimiters-p)) (progn
|
|
(reason-backward-char 1)
|
|
(reason-skip-back-blank-and-comments)))
|
|
((bolp) (progn
|
|
(reason-backward-char 1)
|
|
(reason-skip-back-blank-and-comments)))
|
|
(t (progn (skip-syntax-backward " ")
|
|
(point)))))
|
|
|
|
(defun reason-ro (&rest words)
|
|
"Build a regex matching iff at least a word in WORDS is present."
|
|
(concat "\\<" (regexp-opt words t) "\\>"))
|
|
|
|
(defconst reason-find-phrase-beginning-regexp
|
|
(concat (reason-ro "end" "type" "module" "sig" "struct" "class"
|
|
"exception" "open" "let")
|
|
"\\|^#[ \t]*[a-z][_a-z]*\\>\\|;"))
|
|
|
|
(defun reason-at-phrase-start-p ()
|
|
"Return t if is looking at the beginning of a phrase.
|
|
A phrase starts when a toplevel keyword is at the beginning of a line."
|
|
(or (looking-at "#")
|
|
(looking-at reason-find-phrase-beginning-regexp)))
|
|
|
|
(defun reason-find-phrase-beginning-backward ()
|
|
"Find the beginning of a phrase and return point.
|
|
It scans code backwards, therefore the caller can assume that the
|
|
beginning of the phrase (if found) is always before the starting
|
|
point. No error is signalled and (point-min) is returned when a
|
|
phrease cannot be found."
|
|
(beginning-of-line)
|
|
(while (and (not (bobp)) (not (reason-at-phrase-start-p)))
|
|
(if (reason-inside-block-scope-p)
|
|
(reason-skip-back-to-open-delimiter)
|
|
(re-search-backward reason-find-phrase-beginning-regexp nil 'move)))
|
|
(point))
|
|
|
|
(defun reason-discover-phrase ()
|
|
"Discover a Reason phrase in the buffer."
|
|
;; TODO reason-with-internal-syntax ;; tuareg2 modifies the syntax table (removed for now)
|
|
;; TODO stop-at-and feature for phrase detection (do we need it?)
|
|
;; TODO tuareg2 has some custom logic for module and class (do we need it?)
|
|
(save-excursion
|
|
(let ((case-fold-search nil))
|
|
(reason-skip-blank-and-comments)
|
|
(list (reason-find-phrase-beginning-backward) ;; beginning
|
|
(reason-find-phrase-end) ;; end
|
|
(save-excursion ;; end-with-comment
|
|
(reason-skip-blank-and-comments)
|
|
(point))))))
|
|
|
|
(defun reason-discover-phrase-debug ()
|
|
"Discover a Reason phrase in the buffer (debug mode)."
|
|
(let ((triple (reason-discover-phrase)))
|
|
(message (concat "Evaluating: \"" (reason-fetch-phrase triple) "\""))
|
|
triple))
|
|
|
|
(defun reason-fetch-phrase (triple)
|
|
"Fetch the phrase text given a TRIPLE."
|
|
(let* ((start (nth 0 triple))
|
|
(end (nth 1 triple))) ;; we don't need end-with-comment
|
|
(buffer-substring-no-properties start end)))
|
|
|
|
(defun reason-next-phrase ()
|
|
"Skip to the beginning of the next phrase."
|
|
(cond
|
|
((reason-at-phrase-start-p) (point))
|
|
((eolp) (progn
|
|
(forward-char 1)
|
|
(reason-skip-blank-and-comments)
|
|
(reason-next-phrase)))
|
|
((reason-inside-block-scope-p) (progn (reason-skip-to-close-delimiter)
|
|
(reason-next-phrase)))
|
|
((looking-at ";") (progn
|
|
(forward-char 1)
|
|
(reason-next-phrase)))
|
|
(t (progn (end-of-line)
|
|
(reason-next-phrase)))))
|
|
|
|
(provide 'reason-interaction)
|
|
|
|
;;; reason-interaction.el ends here
|