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
 |