kopia lustrzana https://github.com/c9/core
599 wiersze
25 KiB
EmacsLisp
599 wiersze
25 KiB
EmacsLisp
;;; tern.el --- Tern-powered JavaScript integration -*- lexical-binding: t -*-
|
|
|
|
;; Author: Marijn Haverbeke
|
|
;; URL: http://ternjs.net/
|
|
;; Version: 0.0.1
|
|
;; Package-Requires: ((json "1.2") (cl-lib "0.5") (emacs "24"))
|
|
|
|
;;; Code:
|
|
|
|
(require 'cl-lib)
|
|
(require 'json)
|
|
(require 'url)
|
|
(require 'url-http)
|
|
|
|
(defvar tern-known-port nil)
|
|
(defvar tern-server nil)
|
|
(defvar tern-explicit-port nil)
|
|
(defvar tern-project-dir nil)
|
|
|
|
(defun tern-message (fmt &rest objects)
|
|
(apply 'message fmt objects))
|
|
|
|
(defun tern-req (port doc c)
|
|
(let* ((url-mime-charset-string nil) ; Suppress huge, useless header
|
|
(url-request-method "POST")
|
|
(deactivate-mark nil) ; Prevents json-encode from interfering with shift-selection-mode
|
|
(url-request-data (encode-coding-string (json-encode doc) 'utf-8))
|
|
(url-show-status nil)
|
|
(url (url-parse-make-urlobj "http" nil nil tern-server port "/" nil nil nil)))
|
|
(url-http url #'tern-req-finished (list c))))
|
|
|
|
(defun tern-req-finished (c)
|
|
(defvar url-http-process)
|
|
(defvar url-callback-function)
|
|
(let ((is-error (and (consp c) (eq (car c) :error)))
|
|
(found-body (search-forward "\n\n" nil t))
|
|
(deactivate-mark nil))
|
|
(set-buffer-multibyte t)
|
|
(if (or is-error (not found-body))
|
|
(let ((message (and found-body
|
|
(buffer-substring-no-properties (point) (point-max))))
|
|
(url-callback-function #'ignore))
|
|
(delete-process url-http-process)
|
|
(kill-buffer (current-buffer))
|
|
(funcall (if is-error (cddr c) c)
|
|
(cons (and is-error (cadr c)) message) nil))
|
|
(let ((json (json-read)))
|
|
(delete-process url-http-process)
|
|
(kill-buffer (current-buffer))
|
|
(funcall c nil json)))))
|
|
|
|
(defun tern-project-dir ()
|
|
(or tern-project-dir
|
|
(and (not (buffer-file-name)) (setf tern-project-dir ""))
|
|
(let ((project-dir (file-name-directory (buffer-file-name))))
|
|
(cl-loop for cur = project-dir then (let ((shorter (file-name-directory (substring cur 0 (1- (length cur))))))
|
|
(and (< (length shorter) (length cur)) shorter))
|
|
while cur do
|
|
(when (file-exists-p (expand-file-name ".tern-project" cur))
|
|
(cl-return (setf project-dir cur))))
|
|
(setf tern-project-dir project-dir))))
|
|
|
|
(defun tern-find-server (c &optional ignore-port)
|
|
(cl-block nil
|
|
(when tern-known-port
|
|
(cl-return (if (consp tern-known-port)
|
|
(funcall c nil (cdr tern-known-port))
|
|
(funcall c tern-known-port nil))))
|
|
(if tern-explicit-port
|
|
(funcall c tern-explicit-port nil)
|
|
(unless (buffer-file-name)
|
|
(cl-return (funcall c nil "Buffer is not associated with a file")))
|
|
(let ((deactivate-mark nil)
|
|
(port-file (expand-file-name ".tern-port" (tern-project-dir))))
|
|
(when (file-exists-p port-file)
|
|
(let ((port (string-to-number (with-temp-buffer
|
|
(insert-file-contents port-file)
|
|
(buffer-string)))))
|
|
(unless (eq port ignore-port)
|
|
(setf tern-known-port port)
|
|
(cl-return (funcall c port nil))))))
|
|
(tern-start-server c))))
|
|
|
|
(defvar tern-command
|
|
(let* ((script-file (or load-file-name
|
|
(and (boundp 'bytecomp-filename) bytecomp-filename)
|
|
buffer-file-name))
|
|
(bin-file (expand-file-name "../bin/tern" (file-name-directory (file-truename script-file))))
|
|
(tern-itself (list (if (file-exists-p bin-file) bin-file "tern"))))
|
|
(if (eq system-type 'windows-nt) (cons "node" tern-itself) tern-itself))
|
|
"The command to be run to start the Tern server. Should be a
|
|
list of strings, giving the binary name and arguments.")
|
|
|
|
(defun tern-start-server (c)
|
|
(let* ((default-directory tern-project-dir)
|
|
(cmd (if (member "--strip-crs" tern-command) tern-command (append tern-command '("--strip-crs"))))
|
|
(proc (apply #'start-process "Tern" nil cmd))
|
|
(all-output ""))
|
|
(set-process-query-on-exit-flag proc nil)
|
|
(set-process-sentinel proc (lambda (_proc _event)
|
|
(delete-process proc)
|
|
(setf tern-known-port (cons :failed (concat "Could not start Tern server\n" all-output)))
|
|
(run-at-time "30 sec" nil
|
|
(lambda (buf)
|
|
(with-current-buffer buf
|
|
(when (consp tern-known-port) (setf tern-known-port nil))))
|
|
(current-buffer))
|
|
(funcall c nil tern-known-port)))
|
|
(set-process-filter proc (lambda (proc output)
|
|
(if (not (string-match "Listening on port \\([0-9][0-9]*\\)" output))
|
|
(setf all-output (concat all-output output))
|
|
(setf tern-known-port (string-to-number (match-string 1 output)))
|
|
(set-process-sentinel proc (lambda (proc _event)
|
|
(delete-process proc)
|
|
(setf tern-known-port nil)))
|
|
(set-process-filter proc nil)
|
|
(funcall c tern-known-port nil))))))
|
|
|
|
(defvar tern-command-generation 0)
|
|
(defvar tern-activity-since-command -1)
|
|
(defvar tern-last-point-pos nil)
|
|
|
|
(defvar tern-last-completions nil)
|
|
(defvar tern-last-argument-hints nil)
|
|
(defvar tern-buffer-is-dirty nil)
|
|
|
|
(defun tern-project-relative-file ()
|
|
(substring (buffer-file-name) (length (tern-project-dir))))
|
|
|
|
(defun tern-get-partial-file (at)
|
|
(let* (min-indent start-pos end-pos
|
|
(min-pos (max 0 (- at 2000))))
|
|
(save-excursion
|
|
(goto-char at)
|
|
(cl-loop
|
|
(unless (re-search-backward "\\bfunction\\b" min-pos t) (cl-return))
|
|
(let ((indent (current-indentation))
|
|
(pos (line-beginning-position)))
|
|
(when (or (not min-indent) (< indent min-indent))
|
|
(setf min-indent indent))
|
|
(goto-char pos)
|
|
(when (<= pos min-pos) (cl-return))))
|
|
(unless start-pos (goto-char min-pos) (setf start-pos (line-beginning-position))))
|
|
(save-excursion
|
|
(goto-char (min (+ at 1000) (point-max)))
|
|
(let ((line-beg (line-beginning-position)))
|
|
(setf end-pos (if (<= line-beg at) (line-end-position) line-beg))))
|
|
`((type . "part")
|
|
(name . ,(tern-project-relative-file))
|
|
(offset . ,(1- start-pos))
|
|
(text . ,(buffer-substring-no-properties start-pos end-pos)))))
|
|
|
|
(defun tern-modified-sibling-buffers ()
|
|
(let (found)
|
|
(dolist (buf (buffer-list))
|
|
(when (and (not (eq buf (current-buffer)))
|
|
(buffer-local-value 'tern-mode buf)
|
|
(buffer-local-value 'tern-buffer-is-dirty buf)
|
|
(equal tern-project-dir (buffer-local-value 'tern-project-dir buf)))
|
|
(with-current-buffer buf
|
|
(push `((type . "full")
|
|
(name . ,(tern-project-relative-file))
|
|
(text . ,(buffer-string))) found))))
|
|
(nreverse found)))
|
|
|
|
(defun tern-run-request (f doc)
|
|
(let ((buffer (current-buffer))
|
|
(retrying nil)
|
|
callback runner)
|
|
(setf callback (lambda (port err)
|
|
(if port
|
|
(condition-case err
|
|
(tern-req port doc runner)
|
|
(error (funcall runner (list err) nil)))
|
|
(funcall f err nil))))
|
|
(setf runner (lambda (err data)
|
|
(with-current-buffer buffer
|
|
(cond ((and err (not retrying)
|
|
(or (eq (cl-cadar err) 'connection-failed)
|
|
(eq (caar err) 'file-error)))
|
|
(setf retrying t)
|
|
(let ((old-port tern-known-port))
|
|
(setf tern-known-port nil)
|
|
(if tern-explicit-port
|
|
(funcall callback nil err)
|
|
(tern-find-server callback old-port))))
|
|
(t (funcall f err data))))))
|
|
(tern-find-server callback)))
|
|
|
|
(defun tern-run-query (f query pos &optional mode)
|
|
(when (stringp query) (setf query `((type . ,query))))
|
|
(let ((generation (cl-incf tern-command-generation))
|
|
(doc `((query . ,query)))
|
|
(files (and (eq mode :full-file) (tern-modified-sibling-buffers)))
|
|
file-name
|
|
(pos pos))
|
|
(cond
|
|
((not tern-buffer-is-dirty) (setf file-name (tern-project-relative-file)))
|
|
((and (not (eq mode :full-file)) (> (buffer-size) 8000))
|
|
(push (tern-get-partial-file pos) files)
|
|
(setf file-name "#0")
|
|
(cl-decf pos (cdr (assq 'offset (car files)))))
|
|
(t
|
|
(push `((type . "full") (text . ,(buffer-string)) (name . ,(tern-project-relative-file))) files)
|
|
(setf file-name (tern-project-relative-file))))
|
|
(when files (push `(files . ,(apply #'vector files)) doc))
|
|
(push `(file . ,file-name) (cdr (assq 'query doc)))
|
|
(push `(end . ,(1- pos)) (cdr (assq 'query doc)))
|
|
(tern-run-request
|
|
(lambda (err data)
|
|
(when (< tern-activity-since-command generation)
|
|
(cond ((not err)
|
|
(dolist (file files)
|
|
(when (equal (cdr (assq 'type file)) "full")
|
|
(with-current-buffer (find-file-noselect (expand-file-name (cdr (assq 'name file)) tern-project-dir))
|
|
(setf tern-buffer-is-dirty nil))))
|
|
(funcall f data))
|
|
((not (eq mode :silent)) (tern-message "Request failed: %s" err)))))
|
|
doc)))
|
|
|
|
(defun tern-send-buffer-to-server ()
|
|
(when (buffer-file-name)
|
|
(tern-run-request (lambda (_err _data))
|
|
`((files . [((type . "full")
|
|
(name . ,(tern-project-relative-file))
|
|
(text . ,(buffer-string)))])))))
|
|
|
|
;; Completion
|
|
|
|
(defun tern-completion-at-point-fn ()
|
|
(tern-run-query #'tern-do-complete '((type . "completions") (includeKeywords . t)) (point)))
|
|
|
|
(defun tern-completion-at-point ()
|
|
(or (tern-completion-matches-last)
|
|
;; Do not return a closure, as calling car-safe (e.g. in
|
|
;; completion-at-point) on such an object returns 'closure
|
|
;; instead of nil.
|
|
'tern-completion-at-point-fn))
|
|
|
|
(defun tern-do-complete (data)
|
|
(let ((cs (cl-loop for elt across (cdr (assq 'completions data)) collect elt))
|
|
(start (+ 1 (cdr (assq 'start data))))
|
|
(end (+ 1 (cdr (assq 'end data)))))
|
|
(setf tern-last-completions (list (buffer-substring-no-properties start end) start end cs))
|
|
(let ((completion-in-region-mode-predicate nil))
|
|
(completion-in-region start end cs))))
|
|
|
|
(defun tern-completion-matches-last ()
|
|
(when tern-last-completions
|
|
(cl-destructuring-bind (word start end list) tern-last-completions
|
|
(and (<= end (point-max))
|
|
(equal word (buffer-substring-no-properties start end))
|
|
(if (= (point) end)
|
|
(cdr tern-last-completions)
|
|
(and (>= (point) end)
|
|
(<= (point) (+ end 50))
|
|
(string-match-p "^[a-zA-Z0-9_$]*$" (buffer-substring-no-properties end (point)))
|
|
(let ((new-word (buffer-substring-no-properties start (point))))
|
|
(list start (point)
|
|
(cl-loop for elt in list
|
|
when (eq (compare-strings word 0 (length word) new-word 0 (length word)) t)
|
|
collect elt)))))))))
|
|
|
|
;; Argument hints
|
|
|
|
(defvar tern-update-argument-hints-timer 500 "millisecond.")
|
|
|
|
(defvar tern-update-argument-hints-async nil
|
|
"[internal] If non-nil, `tern-update-argument-hints' will be called later.")
|
|
|
|
(defun tern-update-argument-hints-async ()
|
|
(when tern-update-argument-hints-async
|
|
(cancel-timer tern-update-argument-hints-async))
|
|
(setq tern-update-argument-hints-async
|
|
(run-at-time
|
|
(* 0.001 tern-update-argument-hints-timer) nil
|
|
(lambda ()
|
|
(condition-case err
|
|
(tern-update-argument-hints)
|
|
(t (message "tern-update-argument-hints : %S" err)))
|
|
(setq tern-update-argument-hints-async nil)))))
|
|
|
|
(defun tern-update-argument-hints ()
|
|
(let ((opening-paren (cadr (syntax-ppss))))
|
|
(when (and opening-paren (equal (char-after opening-paren) ?\())
|
|
(if (and tern-last-argument-hints (eq (car tern-last-argument-hints) opening-paren))
|
|
(tern-show-argument-hints)
|
|
(tern-run-query (lambda (data)
|
|
(let ((type (tern-parse-function-type data)))
|
|
(when type
|
|
(setf tern-last-argument-hints (cons opening-paren type))
|
|
(tern-show-argument-hints))))
|
|
`((type . "type")
|
|
(preferFunction . t))
|
|
opening-paren
|
|
:silent)))))
|
|
|
|
(defun tern-skip-matching-brackets (end-chars)
|
|
(let ((depth 0) (end (+ (point) 500)))
|
|
(cl-loop while (< (point) (point-max)) do
|
|
(let ((next (char-after (point))))
|
|
(cond
|
|
((and (<= depth 0) (cl-find next end-chars)) (cl-return t))
|
|
((or (eq next ?\)) (eq next ?\]) (eq next ?\})) (cl-decf depth))
|
|
((or (eq next ?\() (eq next ?\[) (eq next ?\{)) (cl-incf depth))
|
|
((> (point) end) (cl-return nil)))
|
|
(forward-char)))))
|
|
|
|
(defun tern-parse-function-type (data)
|
|
(let ((type (cdr (assq 'type data)))
|
|
(name (or (cdr (assq 'exprName data)) (cdr (assq 'name data)) "fn"))
|
|
(deactivate-mark nil))
|
|
(when (string-match-p "^fn(" type)
|
|
(with-temp-buffer
|
|
(insert type)
|
|
(goto-char 4)
|
|
(let (args retval)
|
|
(cl-loop until (eq (char-after (point)) ?\)) do
|
|
(let ((name (when (looking-at "\\([a-zA-Z0-9_$?]*\\):\\s-*")
|
|
(goto-char (match-end 0))
|
|
(match-string 1)))
|
|
(typestart (point)))
|
|
(tern-skip-matching-brackets '(?\) ?\,))
|
|
(push (cons name (buffer-substring typestart (point))) args))
|
|
(when (eq (char-after (point)) ?\,) (forward-char 2)))
|
|
(when (looking-at ") -> ")
|
|
(setf retval (buffer-substring (+ (point) 5) (point-max))))
|
|
(list name (nreverse args) retval))))))
|
|
|
|
(defun tern-find-current-arg (start)
|
|
(when (< (point) (+ start 500))
|
|
(save-excursion
|
|
(let ((cur-point (point)))
|
|
(goto-char (1+ start))
|
|
(cl-loop for i from 0 do
|
|
(let ((found-end (tern-skip-matching-brackets '(?\) ?\,))))
|
|
(when (>= (point) cur-point) (cl-return i))
|
|
(when (or (not found-end) (looking-at ")")) (cl-return nil))
|
|
(forward-char 1)))))))
|
|
|
|
(defun tern-show-argument-hints ()
|
|
(cl-destructuring-bind (paren . type) tern-last-argument-hints
|
|
(let ((parts ())
|
|
(current-arg (tern-find-current-arg paren)))
|
|
(cl-destructuring-bind (name args ret) type
|
|
(push (propertize name 'face 'font-lock-function-name-face) parts)
|
|
(push "(" parts)
|
|
(cl-loop for arg in args for i from 0 do
|
|
(unless (zerop i) (push ", " parts))
|
|
(let ((name (or (car arg) "?")))
|
|
(push (if (eq i current-arg) (propertize name 'face 'highlight) name) parts))
|
|
(unless (equal (cdr arg) "?")
|
|
(push ": " parts)
|
|
(push (propertize (cdr arg) 'face 'font-lock-type-face) parts)))
|
|
(push ")" parts)
|
|
(when ret
|
|
(push " -> " parts)
|
|
(push (propertize ret 'face 'font-lock-type-face) parts)))
|
|
(let (message-log-max)
|
|
(tern-message (apply #'concat (nreverse parts)))))))
|
|
|
|
;; Refactoring ops
|
|
|
|
(defun tern-do-refactor (data)
|
|
(let ((per-file ())
|
|
(orig-buffer (current-buffer)))
|
|
(cl-loop for change across (cdr (assq 'changes data)) do
|
|
(let ((found (assoc-string (cdr (assq 'file change)) per-file)))
|
|
(unless found (setf found (list (cdr (assq 'file change)))) (push found per-file))
|
|
(push change (cdr found))))
|
|
(cl-loop for (file . changes) in per-file do
|
|
(setf changes (sort changes (lambda (a b) (> (cdr (assq 'start a)) (cdr (assq 'start b))))))
|
|
(find-file (expand-file-name file (tern-project-dir)))
|
|
(cl-loop for change in changes do
|
|
(let ((start (1+ (cdr (assq 'start change))))
|
|
(end (1+ (cdr (assq 'end change)))))
|
|
(delete-region start end)
|
|
(save-excursion
|
|
(goto-char start)
|
|
(insert (cdr (assq 'text change)))))))
|
|
(switch-to-buffer orig-buffer)))
|
|
|
|
(defun tern-rename-variable (new-name)
|
|
(interactive "MNew variable name: ")
|
|
(tern-run-query #'tern-do-refactor `((type . "rename") (newName . ,new-name)) (point) :full-file))
|
|
|
|
;; Highlight references in scope
|
|
|
|
(defvar tern-flash-timeout 0.5 "Delay before the highlight overlay disappears.")
|
|
|
|
(defun tern-flash-region (start end)
|
|
"Temporarily highlight region from START to END."
|
|
(let ((overlay (make-overlay start end)))
|
|
(overlay-put overlay 'face 'highlight)
|
|
(run-with-timer tern-flash-timeout nil 'delete-overlay overlay)))
|
|
|
|
(defun tern-do-highlight (data)
|
|
(cl-loop for ref across (cdr (assq 'refs data)) do
|
|
(let ((file (cdr (assq 'file ref))))
|
|
(when (string= buffer-file-name (expand-file-name file (tern-project-dir)))
|
|
(let ((start (1+ (cdr (assq 'start ref))))
|
|
(end (1+ (cdr (assq 'end ref)))))
|
|
(tern-flash-region start end))))))
|
|
|
|
(defun tern-highlight-refs ()
|
|
(interactive)
|
|
(tern-run-query #'tern-do-highlight "refs" (point)))
|
|
|
|
;; Jump-to-definition
|
|
|
|
(defvar tern-find-definition-stack ())
|
|
|
|
(defun tern-show-definition (data)
|
|
(let* ((file (cdr (assq 'file data)))
|
|
(found (and file (setf file (expand-file-name (cdr (assq 'file data)) (tern-project-dir)))
|
|
(tern-find-position file data))))
|
|
(if found
|
|
(progn
|
|
(push (cons (buffer-file-name) (point)) tern-find-definition-stack)
|
|
(let ((too-long (nthcdr 20 tern-find-definition-stack)))
|
|
(when too-long (setf (cdr too-long) nil)))
|
|
(tern-go-to-position file found))
|
|
(let ((url (cdr (assq 'url data))))
|
|
(if url
|
|
(browse-url url)
|
|
(tern-message "No definition found."))))))
|
|
|
|
(defun tern-at-interesting-expression ()
|
|
(if (member (get-text-property (point) 'face)
|
|
'(font-lock-comment-face font-lock-comment-delimiter-face font-lock-string-face))
|
|
nil
|
|
(let ((around (buffer-substring-no-properties (max 1 (1- (point))) (min (1+ (point)) (point-max)))))
|
|
(string-match "\\sw\\|)\\|]\\|_" around))))
|
|
|
|
(defun tern-find-definition (&optional prompt-var)
|
|
(interactive)
|
|
(let ((varname (and (or prompt-var (not (tern-at-interesting-expression)))
|
|
(read-from-minibuffer "Variable: "))))
|
|
(push-mark)
|
|
(tern-run-query #'tern-show-definition `((type . "definition") (variable . ,varname)) (point))))
|
|
|
|
(defun tern-find-definition-by-name ()
|
|
(interactive)
|
|
(tern-find-definition t))
|
|
|
|
(defun tern-find-position (file data)
|
|
(with-current-buffer (find-file-noselect file)
|
|
(let* ((start (1+ (cdr (assq 'start data))))
|
|
(cx-start (- start (cdr (assq 'contextOffset data))))
|
|
(cx (cdr (assq 'context data)))
|
|
(cx-end (+ cx-start (length cx))))
|
|
(if (and (<= (point-max) cx-end) (equal (buffer-substring-no-properties cx-start cx-end) cx))
|
|
start
|
|
(let (nearest nearest-dist)
|
|
(save-excursion
|
|
(goto-char (point-min))
|
|
(cl-loop
|
|
(unless (search-forward cx nil t) (cl-return))
|
|
(let* ((here (- (point) (length cx)))
|
|
(dist (abs (- cx-start here))))
|
|
(when (or (not nearest-dist) (< dist nearest-dist))
|
|
(setf nearest here nearest-dist dist)))))
|
|
(when nearest
|
|
(+ nearest (- start cx-start))))))))
|
|
|
|
(defun tern-pop-find-definition ()
|
|
(interactive)
|
|
(when tern-find-definition-stack
|
|
(cl-destructuring-bind (file . pos) (pop tern-find-definition-stack)
|
|
(tern-go-to-position file pos))))
|
|
|
|
(defun tern-go-to-position (file pos)
|
|
(find-file file)
|
|
(goto-char (min pos (point-max)))
|
|
(setf tern-last-point-pos (point)))
|
|
|
|
;; Query type
|
|
|
|
(defun tern-get-type ()
|
|
(interactive)
|
|
(tern-run-query (lambda (data) (tern-message (or (cdr (assq 'type data)) "Not found")))
|
|
"type"
|
|
(point)))
|
|
|
|
;; Display docs
|
|
|
|
(defvar tern-last-docs-url nil)
|
|
(defun tern-get-docs ()
|
|
(interactive)
|
|
(if (and tern-last-docs-url (eq last-command 'tern-get-docs))
|
|
(progn
|
|
(browse-url tern-last-docs-url)
|
|
(setf tern-last-docs-url nil))
|
|
(tern-run-query (lambda (data)
|
|
(let ((url (cdr (assq 'url data))) (doc (cdr (assq 'doc data))))
|
|
(cond (doc
|
|
(setf tern-last-docs-url url)
|
|
(tern-message doc))
|
|
(url
|
|
(browse-url url))
|
|
(t (tern-message "Not found")))))
|
|
"documentation"
|
|
(point))))
|
|
|
|
;; Connection management
|
|
|
|
;;;###autoload
|
|
(defun tern-use-server (port server)
|
|
(interactive "nPort to connect to: \nsServer: ")
|
|
(setf tern-explicit-port port)
|
|
(setf tern-known-port nil)
|
|
(setf tern-server (if (string= server "") "127.0.0.1" server)))
|
|
|
|
;; Mode plumbing
|
|
|
|
(defun tern-after-change (start end prev-length)
|
|
"Track the dirty area of the buffer."
|
|
(if tern-buffer-is-dirty
|
|
(setf tern-buffer-is-dirty (cons (min start (car tern-buffer-is-dirty))
|
|
(max end (cdr tern-buffer-is-dirty))))
|
|
(setf tern-buffer-is-dirty (cons start end))))
|
|
|
|
(defvar tern-idle-time 2.5
|
|
"The time Emacs is allowed to idle before updating Tern's representation of the file.")
|
|
(defvar tern-idle-timer nil
|
|
"The timer on which `tern-reparse-on-idle' runs.")
|
|
(defun tern-reparse-on-idle ()
|
|
"Do some mode plumbing and refresh tern's representation of the buffer."
|
|
(when tern-mode
|
|
(setf tern-last-point-pos nil)
|
|
(when (and tern-last-argument-hints tern-buffer-is-dirty
|
|
(<= (car tern-buffer-is-dirty) (car tern-last-argument-hints)))
|
|
(setf tern-last-argument-hints nil))
|
|
(when (and tern-last-completions tern-buffer-is-dirty
|
|
(<= (car tern-buffer-is-dirty) (cadr tern-last-completions)))
|
|
(setf tern-last-completions nil))
|
|
(tern-send-buffer-to-server)))
|
|
|
|
(defun tern-post-command ()
|
|
(unless (eq (point) tern-last-point-pos)
|
|
(setf tern-last-point-pos (point))
|
|
(setf tern-activity-since-command tern-command-generation)
|
|
(tern-update-argument-hints-async)))
|
|
|
|
(defun tern-left-buffer ()
|
|
(when (and tern-buffer-is-dirty (not (buffer-file-name (car (buffer-list)))))
|
|
(setf tern-buffer-is-dirty nil)
|
|
(let ((buffer-list-update-hook ()))
|
|
(tern-send-buffer-to-server))))
|
|
|
|
(defvar tern-mode-keymap (make-sparse-keymap))
|
|
(define-key tern-mode-keymap [(meta ?.)] 'tern-find-definition)
|
|
(define-key tern-mode-keymap [(control meta ?.)] 'tern-find-definition-by-name)
|
|
(define-key tern-mode-keymap [(meta ?,)] 'tern-pop-find-definition)
|
|
(define-key tern-mode-keymap [(control ?c) (control ?r)] 'tern-rename-variable)
|
|
(define-key tern-mode-keymap [(control ?c) (control ?c)] 'tern-get-type)
|
|
(define-key tern-mode-keymap [(control ?c) (control ?d)] 'tern-get-docs)
|
|
|
|
;;;###autoload
|
|
(define-minor-mode tern-mode
|
|
"Minor mode binding to the Tern JavaScript analyzer"
|
|
nil
|
|
" Tern"
|
|
tern-mode-keymap
|
|
(if tern-mode (tern-mode-enable) (tern-mode-disable)))
|
|
|
|
(defun tern-mode-enable ()
|
|
(set (make-local-variable 'tern-known-port) nil)
|
|
(set (make-local-variable 'tern-server) "127.0.0.1")
|
|
(set (make-local-variable 'tern-explicit-port) nil)
|
|
(set (make-local-variable 'tern-project-dir) nil)
|
|
(set (make-local-variable 'tern-last-point-pos) nil)
|
|
(set (make-local-variable 'tern-last-completions) nil)
|
|
(set (make-local-variable 'tern-last-argument-hints) nil)
|
|
(set (make-local-variable 'tern-buffer-is-dirty) (and (buffer-modified-p) (cons (point-min) (point-max))))
|
|
(make-local-variable 'completion-at-point-functions)
|
|
(push 'tern-completion-at-point completion-at-point-functions)
|
|
(add-hook 'after-change-functions 'tern-after-change nil t)
|
|
(when (null tern-idle-timer)
|
|
(setq tern-idle-timer
|
|
(run-with-idle-timer tern-idle-time t #'tern-reparse-on-idle)))
|
|
(add-hook 'post-command-hook 'tern-post-command nil t)
|
|
(add-hook 'buffer-list-update-hook 'tern-left-buffer nil t)
|
|
(tern-send-buffer-to-server))
|
|
|
|
(defun tern-mode-disable ()
|
|
(setf completion-at-point-functions
|
|
(remove 'tern-completion-at-point completion-at-point-functions))
|
|
(unless (null tern-idle-timer)
|
|
(cancel-timer tern-idle-timer)
|
|
(setq tern-idle-timer nil))
|
|
(remove-hook 'after-change-functions 'tern-after-change t)
|
|
(remove-hook 'post-command-hook 'tern-post-command t)
|
|
(remove-hook 'buffer-list-update-hook 'tern-left-buffer t))
|
|
|
|
(provide 'tern)
|
|
|
|
;;; tern.el ends here
|