summaryrefslogtreecommitdiff
path: root/elpa/tide-20220514.614/tide.el
diff options
context:
space:
mode:
authormattkae <mattkae@protonmail.com>2022-06-07 08:23:47 -0400
committermattkae <mattkae@protonmail.com>2022-06-07 08:23:47 -0400
commitbd18a38c2898548a3664a9ddab9f79c84f2caf4a (patch)
tree95b9933376770381bd8859782ae763be81c2d72b /elpa/tide-20220514.614/tide.el
parentb07628dddf418d4f47b858e6c35fd3520fbaeed2 (diff)
parentef160dea332af4b4fe5e2717b962936c67e5fe9e (diff)
Merge conflict
Diffstat (limited to 'elpa/tide-20220514.614/tide.el')
-rw-r--r--elpa/tide-20220514.614/tide.el3138
1 files changed, 0 insertions, 3138 deletions
diff --git a/elpa/tide-20220514.614/tide.el b/elpa/tide-20220514.614/tide.el
deleted file mode 100644
index 588f6b8..0000000
--- a/elpa/tide-20220514.614/tide.el
+++ /dev/null
@@ -1,3138 +0,0 @@
-;;; tide.el --- Typescript Interactive Development Environment -*- lexical-binding: t -*-
-
-;; Copyright (C) 2015 Anantha Kumaran.
-
-;; Author: Anantha kumaran <ananthakumaran@gmail.com>
-;; URL: http://github.com/ananthakumaran/tide
-;; Version: 4.5.4
-;; Keywords: typescript
-;; Package-Requires: ((emacs "25.1") (dash "2.10.0") (s "1.11.0") (flycheck "27") (typescript-mode "0.1") (cl-lib "0.5"))
-
-;; This program is free software: you can redistribute it and/or modify it
-;; under the terms of the GNU General Public License as published by
-;; the Free Software Foundation, either version 3 of the License, or
-;; (at your option) any later version.
-
-;; This program is distributed in the hope that it will be useful, but
-;; WITHOUT ANY WARRANTY; without even the implied warranty of
-;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-;; General Public License for more details.
-
-;; You should have received a copy of the GNU General Public License
-;; along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-;; This file is not part of GNU Emacs.
-
-;;; Commentary:
-
-;;; Code:
-
-(require 'typescript-mode)
-(require 'etags)
-(require 'json)
-(require 'cl-lib)
-(require 'eldoc)
-(require 'dash)
-(require 's)
-(require 'flycheck)
-(require 'imenu)
-(require 'thingatpt)
-(require 'tide-lv)
-(require 'tabulated-list)
-(require 'xref)
-
-;; Silence compiler warnings
-
-(defvar js2-basic-offset)
-(defvar js-indent-level)
-(defvar js3-indent-level)
-(defvar web-mode-code-indent-offset)
-(defvar sgml-basic-offset)
-(defvar company-backends)
-
-(declare-function company-grab "company.el" (regexp &optional expression limit))
-(declare-function company-grab-symbol-cons "company.el" (idle-begin-after-re &optional max-len))
-(declare-function company-begin-backend "company.el" (backend &optional callback))
-(declare-function company-in-string-or-comment "company.el" nil)
-
-(defgroup tide nil
- "TypeScript Interactive Development Environment."
- :prefix "tide-"
- :group 'tools)
-
-(defcustom tide-sync-request-timeout 2
- "The number of seconds to wait for a sync response."
- :type 'integer
- :group 'tide)
-
-(defcustom tide-tsserver-flags nil
- "List of additional flags to provide when starting tsserver."
- :type '(repeat string)
- :group 'tide)
-
-(defcustom tide-tsserver-process-environment '()
- "List of extra environment variables to use when starting tsserver."
- :type '(repeat string)
- :group 'tide)
-
-(defcustom tide-tsserver-executable nil
- "Name of tsserver executable to run instead of the bundled tsserver.
-
-This may either be an absolute path or a relative path. Relative
-paths are resolved against the project root directory.
-
-Note that this option only works with TypeScript version 2.0 and
-above."
- :type '(choice (const nil) string)
- :group 'tide)
-
-(defcustom tide-tscompiler-executable nil
- "Name of tsc executable.
-
-This may either be an absolute path or a relative path. Relative
-paths are resolved against the project root directory."
- :type '(choice (const nil) string)
- :group 'tide)
-
-(defcustom tide-node-executable "node"
- "Name or path of the node executable binary file."
- :type '(choice (const nil) string)
- :group 'tide)
-
-(defcustom tide-node-flags nil
- "List of flags to provide to node when starting tsserver.
-
-Useful for large TypeScript codebases which need to set
-max-old-space-size to a higher value."
- :type '(repeat string)
- :group 'tide)
-
-(defcustom tide-post-code-edit-hook nil
- "Hook run after code edits are applied in a buffer."
- :type 'hook
- :group 'tide)
-
-(defcustom tide-sort-completions-by-kind nil
- "Whether completions should be sorted by kind."
- :type 'boolean
- :group 'tide)
-
-(defcustom tide-format-options '()
- "Format options plist."
- :type '(plist :value-type sexp)
- :group 'tide)
-
-(defcustom tide-user-preferences
- '(:includeCompletionsForModuleExports t :includeCompletionsWithInsertText t :allowTextChangesInNewFiles t :generateReturnInDocTemplate t)
- "User preference plist used on the configure request.
-
-Check https://github.com/Microsoft/TypeScript/blob/17eaf50b/src/server/protocol.ts#L2684
-for the full list of available options."
- :type '(plist :value-type sexp)
- :group 'tide)
-
-(defcustom tide-disable-suggestions nil
- "Disable suggestions.
-
-If set to non-nil, suggestions will not be shown in flycheck
-errors and tide-project-errors buffer."
- :type 'boolean
- :group 'tide)
-
-(defcustom tide-completion-setup-company-backend t
- "Add `company-tide' to `company-backends'."
- :type 'boolean
- :group 'tide)
-
-(defcustom tide-completion-ignore-case nil
- "CASE will be ignored in completion if set to non-nil."
- :type 'boolean
- :group 'tide)
-
-(defcustom tide-completion-show-source nil
- "Completion dropdown will contain completion source if set to non-nil."
- :type 'boolean
- :group 'tide)
-
-(defcustom tide-completion-fuzzy nil
- "Allow fuzzy completion.
-
-By default only candidates with exact prefix match are shown. If
-set to non-nil, candidates with match anywhere inside the name
-are shown."
- :type 'boolean
- :group 'tide)
-
-(defcustom tide-completion-detailed nil
- "Completion dropdown will contain detailed method information if set to non-nil."
- :type 'boolean
- :group 'tide)
-
-(defcustom tide-completion-enable-autoimport-suggestions t
- "Whether to include external module exports in completions."
- :type 'boolean
- :group 'tide)
-
-(defcustom tide-enable-xref t
- "Whether to enable xref integration."
- :type 'boolean
- :group 'tide)
-
-(defcustom tide-navto-item-filter #'tide-navto-item-filter-default
- "The filter for items returned by tide-nav. Defaults to class, interface, type, enum"
- :type 'function
- :group 'tide)
-
-(defface tide-file
- '((t (:inherit font-lock-type-face)))
- "Face for file names in references output."
- :group 'tide)
-
-(defface tide-line-number
- '((t (:inherit compilation-line-number)))
- "Face for line numbers in references output."
- :group 'tide)
-
-(defface tide-match
- '((t (:inherit match)))
- "Face for matched symbol in references output."
- :group 'tide)
-
-(defface tide-imenu-type-face
- '((t (:inherit font-lock-type-face)))
- "Face for type in imenu list."
- :group 'tide)
-
-(defface tide-choice-face
- '((t (:inherit font-lock-warning-face)))
- "Face for choices used in popup window."
- :group 'tide)
-
-(defcustom tide-jump-to-definition-reuse-window t
- "Reuse existing window when jumping to definition."
- :type 'boolean
- :group 'tide)
-
-(defcustom tide-imenu-flatten nil
- "Imenu index will be flattened if set to non-nil."
- :type 'boolean
- :group 'tide)
-
-(defcustom tide-allow-popup-select '(code-fix refactor)
- "The list of commands where popup selection is allowed."
- :type '(set (const code-fix) (const jump-to-implementation) (const refactor))
- :group 'tide)
-
-(defcustom tide-always-show-documentation nil
- "Show the documentation window even if only type information is available."
- :type 'boolean
- :group 'tide)
-
-(defcustom tide-server-max-response-length 102400
- "Maximum allowed response length from tsserver. Any response greater than this would be ignored."
- :type 'integer
- :group 'tide)
-
-(defcustom tide-tsserver-locator-function #'tide-tsserver-locater-npmlocal-projectile-npmglobal
- "Function used by tide to locate tsserver."
- :type 'function
- :group 'tide)
-
-(defcustom tide-project-cleanup-delay 60
- "The number of idle seconds to wait before cleaning up unused tsservers.
-Use `nil` to disable automatic cleanups. See also `tide-do-cleanups`."
- :type '(choice (const nil) integer)
- :group 'tide)
-
-(defcustom tide-tsserver-start-method 'immediate
- "The method by which tide starts tsserver. `immediate' causes tide to start a tsserver instance
-as soon as `tide-mode' is turned on. `manual' means that tide will start a tsserver only when the
-user manually starts one."
- :type '(choice (const :tag "Start immediately." immediate)
- (const :tag "Require manual start." manual))
- :group 'tide)
-
-(defcustom tide-default-mode "TS"
- "The default mode to open buffers not backed by files (e.g. Org
-source blocks) in."
- :type '(choice (const "TS") (const "TSX") (const "JS")(const "JSX"))
- :group 'tide)
-
-(defcustom tide-recenter-after-jump t
- "Recenter buffer after jumping to definition"
- :type 'boolean
- :group 'tide)
-
-(defcustom tide-jump-to-fallback #'tide-jump-to-fallback-not-given
- "The fallback jump function to use when implementations aren't available."
- :type 'function
- :group 'tide)
-
-(defcustom tide-filter-out-warning-completions nil
- "Completions whose `:kind' property is \"warning\" will be filtered out if set to non-nil.
-This option is useful for Javascript code completion, because tsserver often returns a lot of irrelevant
-completions whose `:kind' property is \"warning\" for Javascript code. You can fix this behavior by setting
-this variable to non-nil value for Javascript buffers using `setq-local' macro."
- :type 'boolean
- :group 'tide
- :safe #'booleanp)
-
-(defcustom tide-native-json-parsing (and (>= emacs-major-version 27)
- (functionp 'json-serialize)
- (functionp 'json-parse-buffer)
- (functionp 'json-parse-string))
- "Use native JSON parsing (only emacs >= 27)."
- :type 'boolean
- :group 'tide)
-
-(defcustom tide-save-buffer-after-code-edit t
- "Save the buffer after applying code edits."
- :type 'boolean
- :group 'tide)
-
-(defconst tide--minimal-emacs
- "25.1"
- "This is the oldest version of Emacs that tide supports.")
-
-(defmacro tide-def-permanent-buffer-local (name &optional init-value)
- "Declare NAME as buffer local variable."
- `(progn
- (defvar ,name ,init-value)
- (make-variable-buffer-local ',name)
- (put ',name 'permanent-local t)))
-
-(defvar tide-supported-modes '(typescript-mode web-mode js-mode js2-mode js2-jsx-mode js3-mode rjsx-mode))
-
-(defvar tide-server-buffer-name "*tide-server*")
-(defvar tide-request-counter 0)
-(defvar tide-project-configs (make-hash-table :test 'equal))
-(defvar tide-max-response-length-error-message
- "Response length from tsserver is greater than maximum allowed response.")
-
-(tide-def-permanent-buffer-local tide-project-root nil)
-(tide-def-permanent-buffer-local tide-buffer-dirty nil)
-(tide-def-permanent-buffer-local tide-buffer-tmp-file nil)
-(tide-def-permanent-buffer-local tide-active-buffer-file-name nil)
-(tide-def-permanent-buffer-local tide-require-manual-setup nil)
-
-(defvar tide-servers (make-hash-table :test 'equal))
-(defvar tide-response-callbacks (make-hash-table :test 'equal))
-
-(defvar tide-source-root-directory (file-name-directory (or load-file-name buffer-file-name)))
-(defvar tide-tsserver-directory (expand-file-name "tsserver" tide-source-root-directory))
-
-(defun tide-project-root ()
- "Project root folder determined based on the presence of tsconfig.json."
- (or
- tide-project-root
- (let ((root (or (locate-dominating-file default-directory "tsconfig.json")
- (locate-dominating-file default-directory "jsconfig.json"))))
- (unless root
- (message (tide-join (list "Couldn't locate project root folder with"
- " a tsconfig.json or jsconfig.json file. Using '"
- default-directory "' as project root.")))
- (setq root default-directory))
- (let ((full-path (expand-file-name root)))
- (setq tide-project-root full-path)
- full-path))))
-
-(defun tide-project-name ()
- (let ((full-path (directory-file-name (tide-project-root))))
- (concat (file-name-nondirectory full-path) "-" (substring (md5 full-path) 0 10))))
-
-(defun tide-buffer-file-name ()
- "Returns the path to either the currently open file or the
-current buffer's parent. This is needed to support indirect
-buffers, as they don't set `buffer-file-name' correctly."
- (buffer-file-name (or (and (bound-and-true-p edit-indirect--overlay)
- (overlay-buffer edit-indirect--overlay))
- (and (bound-and-true-p org-src--overlay)
- (overlay-buffer org-src--overlay))
- ;; Needed for org-mode 8.x compatibility
- (and (bound-and-true-p org-edit-src-overlay)
- (overlay-buffer org-edit-src-overlay))
- (buffer-base-buffer))))
-
-;;; Compatibility
-
-(defvar tide-tsserver-unsupported-commands (make-hash-table :test 'equal))
-
-(defun tide-mark-as-unsupported (command)
- (puthash
- (tide-project-name)
- (cl-pushnew
- command
- (gethash (tide-project-name) tide-tsserver-unsupported-commands '()))
- tide-tsserver-unsupported-commands))
-
-(defun tide-unsupported-p (command)
- (member command (gethash (tide-project-name) tide-tsserver-unsupported-commands '())))
-
-(defmacro tide-fallback-if-not-supported (new-command new old cb)
- `(if (tide-unsupported-p ,new-command)
- (,old ,cb)
- (,new
- (lambda (response)
- (if (tide-command-unknown-p response)
- (progn
- (tide-mark-as-unsupported ,new-command)
- (,old ,cb))
- (funcall ,cb response))))))
-
-(defun tide--emacs-at-least (version)
- "Return t if Emacs is at least at version VERSION. Return nil, otherwise."
- (not (version< emacs-version version)))
-
-;;; Helpers
-
-(defun tide-safe-json-read-file (filename)
- (condition-case nil
- (if tide-native-json-parsing
- (with-temp-buffer
- (insert-file-contents filename)
- (goto-char (point-min))
- (json-parse-buffer :object-type 'plist :null-object json-null :false-object json-false))
- (let ((json-object-type 'plist))
- (json-read-file filename)))
- (error '())))
-
-(defun tide-safe-json-read-string (string)
- (condition-case nil
- (if tide-native-json-parsing
- (json-parse-string string :object-type 'plist :null-object json-null :false-object json-false)
- (let ((json-object-type 'plist))
- (json-read-from-string string)))
- (error '())))
-
-(defun tide-json-read-object ()
- (if tide-native-json-parsing
- (json-parse-buffer :object-type 'plist :null-object json-null :false-object json-false :array-type 'list)
- (let ((json-object-type 'plist)
- (json-array-type 'list))
- (json-read-object))))
-
-(defun tide-json-encode (obj)
- "Encode OBJ into a JSON string. JSON arrays must be represented with vectors."
- (if tide-native-json-parsing
- (json-serialize obj :null-object json-null :false-object json-false)
- (json-encode obj)))
-
-(defun tide-plist-get (list &rest args)
- (cl-reduce
- (lambda (object key)
- (when object
- (plist-get object key)))
- args
- :initial-value list))
-
-(eval-and-compile
- (defun tide-plist-map (fn plist)
- (when (and plist (not (cl-evenp (length plist))))
- (error "Invalid plist %S" plist))
- (-map (-lambda ((key value)) (funcall fn key value)) (-partition 2 plist))))
-
-
-(defun tide-combine-plists (&rest plists)
- "Create a single property list from all plists in PLISTS.
-The process starts by copying the first list, and then setting properties
-from the other lists. Settings in the last list are the most significant
-ones and overrule settings in the other lists."
- (let ((rtn (copy-sequence (pop plists)))
- p v ls)
- (while plists
- (setq ls (pop plists))
- (while ls
- (setq p (pop ls) v (pop ls))
- (setq rtn (plist-put rtn p v))))
- rtn))
-
-(defun tide-get-file-buffer (file &optional new-file)
- "Returns a buffer associated with a file. This will return the
-current buffer if it matches `file'. This way we can support
-temporary and indirect buffers."
- (cond
- ((equal file (tide-buffer-file-name)) (current-buffer))
- ((file-exists-p file) (find-file-noselect file))
- (new-file (let ((buffer (create-file-buffer file)))
- (with-current-buffer buffer
- (set-visited-file-name file)
- (basic-save-buffer)
- (display-buffer buffer t))
- buffer))
- (t (error "Invalid file %S" file))))
-
-(defun tide-response-success-p (response)
- (and response (equal (plist-get response :success) t)))
-
-(defun tide-command-unknown-p (response)
- (and response (string-equal (plist-get response :command) "unknown")))
-
-(defun tide-tsserver-version-not-supported ()
- (error "Only tsserver 2.0 or greater is supported. Upgrade your tsserver or use older version of tide."))
-
-(defun tide-tsserver-feature-not-supported (min-version)
- (error "tsserver %S or greater is required for this feature." min-version))
-
-(defmacro tide-on-response-success (response &optional options &rest body)
- "BODY must be a single expression if OPTIONS is not passed."
- (declare (indent 2))
- (unless body
- (setq body `(,options))
- (setq options '()))
- (tide-plist-map
- (lambda (key _value)
- (unless (member key '(:ignore-empty :min-version))
- (error "Invalid options %S" options)))
- options)
- (let ((ignore-empty-response (plist-get options :ignore-empty))
- (min-version (plist-get options :min-version)))
- `(cond ((and ,min-version (tide-command-unknown-p ,response)) (tide-tsserver-feature-not-supported ,min-version))
- ((tide-response-success-p ,response)
- (progn
- ,@body))
- (t
- (-when-let (msg (plist-get ,response :message))
- (unless (and ,ignore-empty-response (string-equal msg "No content available."))
- (message "%s" msg))
- nil)))))
-
-(defmacro tide-on-response-success-callback (response &optional options &rest body)
- (declare (indent 2))
- `(lambda (,response)
- (tide-on-response-success ,response ,options
- ,@body)))
-
-(defun tide-join (list)
- (mapconcat 'identity list ""))
-
-(defun tide-each-buffer (project-name fn)
- "Callback FN for each buffer within PROJECT-NAME with tide-mode enabled."
- (-each (buffer-list)
- (lambda (buffer)
- (with-current-buffer buffer
- (when (and (bound-and-true-p tide-mode)
- (equal (tide-project-name) project-name))
- (funcall fn))))))
-
-(defun tide-first-buffer (project-name fn)
- "Callback FN for the first buffer within PROJECT-NAME with tide-mode enabled."
- (-when-let (buffer (-first (lambda (buffer)
- (with-current-buffer buffer
- (and (bound-and-true-p tide-mode)
- (equal (tide-project-name) project-name))))
- (buffer-list)))
- (with-current-buffer buffer
- (funcall fn))))
-
-(defun tide-line-number-at-pos (&optional pos)
- (let ((p (or pos (point))))
- (if (= (point-min) 1)
- (line-number-at-pos p)
- (save-excursion
- (save-restriction
- (widen)
- (line-number-at-pos p))))))
-
-(defun tide-current-offset ()
- "Number of characters present from the begining of line to cursor in current line.
-
-Offset is one based."
- (1+ (- (point) (line-beginning-position))))
-
-(defun tide-offset (pos)
- (save-excursion
- (save-restriction
- (widen)
- (goto-char pos)
- (tide-current-offset))))
-
-(defun tide-span-to-position (span)
- (save-excursion
- (save-restriction
- (widen)
- (goto-char (point-min))
- (forward-line (1- (plist-get span :line)))
- (beginning-of-line)
- (forward-char (1- (plist-get span :offset)))
- (point))))
-
-(defun tide-file-span-first-line-text (file-span &optional string)
- (let (end-offset text)
- (save-excursion
- (with-current-buffer (tide-get-file-buffer (plist-get file-span :file))
- (tide-move-to-location (plist-get file-span :start))
- (when string
- (search-forward string (tide-location-to-point (plist-get file-span :end)))
- (setq end-offset (current-column)))
- (setq text (substring-no-properties (replace-regexp-in-string "\n" "" (thing-at-point 'line))))
- (when string
- (put-text-property (- end-offset (length string)) end-offset 'face 'tide-match text))
- text))))
-
-(defun tide-display-help-buffer (feature body)
- (let ((buffer (tide-make-help-buffer feature body)))
- (display-buffer buffer t)
- (if help-window-select
- (progn
- (pop-to-buffer buffer)
- (message "Type \"q\" to restore previous buffer"))
- (message (concat "Type \"q\" in the " feature " buffer to close it")))))
-
-(defun tide-make-help-buffer (feature body)
- (with-current-buffer (get-buffer-create (concat "*tide-" feature "*"))
- (setq buffer-read-only t)
- (let ((inhibit-read-only t))
- (erase-buffer)
- (when body
- (save-excursion
- (tide-insert body))))
- (local-set-key (kbd "q") #'quit-window)
- (current-buffer)))
-
-
-(defvar tide-alphabets '(?a ?s ?d ?f ?j ?k ?l))
-
-(defun tide-popup-select-item (prompt list)
- (let ((hints (-map-indexed
- (lambda (i item)
- (concat (propertize (char-to-string (nth i tide-alphabets)) 'face 'tide-choice-face)
- " "
- item))
- list)))
- (unwind-protect
- (progn
- (tide-lv-message (mapconcat 'identity hints "\n"))
- (let ((selected (read-char-choice prompt (-take (length list) tide-alphabets))))
- (nth (-find-index (lambda (char) (eql selected char)) tide-alphabets) list)))
- (tide-lv-delete-window))))
-
-(defun tide-completing-read-select-item (prompt list)
- (completing-read prompt list nil t))
-
-(defun tide-can-use-popup-p (feature)
- (member feature tide-allow-popup-select))
-
-(defun tide-select-item-from-list (prompt list label-fn allow-popup)
- (let ((collection (make-hash-table :test 'equal)))
- (dolist (item list)
- (puthash (funcall label-fn item) item collection))
- (let ((selected-text
- (if (and (<= (length list) (length tide-alphabets)) allow-popup)
- (tide-popup-select-item prompt (hash-table-keys collection))
- (tide-completing-read-select-item prompt (hash-table-keys collection)))))
- (gethash selected-text collection))))
-
-
-(defun tide-command-to-string (program args)
- (with-temp-buffer
- (apply #'process-file (-concat (list program nil t nil) args))
- (buffer-string)))
-
-;;; Events
-
-(defvar tide-event-listeners (make-hash-table :test 'equal))
-
-(defun tide-set-event-listener (listener)
- (puthash (tide-project-name) (cons (current-buffer) listener) tide-event-listeners))
-
-(defun tide-clear-event-listener ()
- (remhash (tide-project-name) tide-event-listeners))
-
-;;; Server
-
-(defun tide-current-server ()
- (gethash (tide-project-name) tide-servers))
-
-(defun tide-next-request-id ()
- (number-to-string (cl-incf tide-request-counter)))
-
-(defun tide-dispatch-response (response)
- (let* ((request-id (plist-get response :request_seq))
- (callback (gethash request-id tide-response-callbacks)))
- (when callback
- (let ((buffer (car callback)))
- (when (buffer-live-p buffer)
- (with-current-buffer buffer
- (apply (cdr callback) (list response)))))
- (remhash request-id tide-response-callbacks))))
-
-(defun tide-dispatch-event (event)
- (-when-let (listener (gethash (tide-project-name) tide-event-listeners))
- (with-current-buffer (car listener)
- (apply (cdr listener) (list event)))))
-
-(defun tide-dispatch (response)
- (cl-case (intern (plist-get response :type))
- ((response) (tide-dispatch-response response))
- ((event) (tide-dispatch-event response))))
-
-(defun tide-send-command (name args &optional callback)
- (unless (tide-current-server)
- (error "Server does not exist. Run M-x tide-restart-server to start it again"))
-
- (tide-sync-buffer-contents)
-
- (let* ((request-id (tide-next-request-id))
- (command `(:command ,name :seq ,request-id :arguments ,args))
- (json-encoding-pretty-print nil)
- (encoded-command (tide-json-encode command))
- (payload (concat encoded-command "\n")))
- (process-send-string (tide-current-server) payload)
- (when callback
- (puthash request-id (cons (current-buffer) callback) tide-response-callbacks))))
-
-(defun tide-seconds-elapsed-since (time)
- (time-to-seconds (time-subtract (current-time) time)))
-
-(defun tide-send-command-sync (name args)
- (let* ((start-time (current-time))
- (response nil))
- (tide-send-command name args (lambda (resp) (setq response resp)))
- (while (not response)
- (accept-process-output nil 0.01)
- (when (> (tide-seconds-elapsed-since start-time) tide-sync-request-timeout)
- (error "Sync request timed out %s" name)))
- response))
-
-(defun tide-net-filter (process data)
- (with-current-buffer (process-buffer process)
- (goto-char (point-max))
- (insert data))
- (tide-decode-response process))
-
-(defun tide-net-sentinel (process message)
- (let ((project-name (process-get process 'project-name)))
- (message "(%s) tsserver exits: %s." project-name (string-trim message))
- (ignore-errors
- (kill-buffer (process-buffer process)))
- (tide-cleanup-project project-name)))
-
-(defun tide--npm-local ()
- "Return a single path to the package-local typescript package directory or nil."
-
- (-when-let (packages-folder (locate-dominating-file default-directory "package.json"))
- (concat packages-folder "node_modules/typescript/lib/")))
-
-(defun tide--npm-global ()
- "Return a single path to the global typescript package directory or nil."
-
- (if (eq system-type 'windows-nt)
- (concat (getenv "appdata") "\\npm\\node_modules")
- "/usr/lib/node_modules/typescript/lib/"))
-
-(defun tide--npm-global-usrlocal ()
- "Return a single path to the global typescript package directory or nil."
-
- ;; this check does not apply to windows.
- (if (eq system-type 'windows-nt)
- nil
- "/usr/local/lib/node_modules/typescript/lib/"))
-
-(defun tide--project-package ()
- "Return the package's node_module bin directory using projectile's project root or nil."
- (when (and (functionp 'projectile-project-p)
- (functionp 'projectile-project-root)
- (projectile-project-p))
- (concat (projectile-project-root) "node_modules/typescript/lib")))
-
-(defconst tide--tscompiler "tsc.js"
- "File-name of the typescript compiler.")
-
-(defun tide--locate-tscompiler (path)
- "Locate the typescript compiler in PATH.
-Return a string representing the existing full path or nil."
- (let ((exe (expand-file-name tide--tscompiler path)))
- (when (file-exists-p exe) exe)))
-
-(defun tide-tscompiler-locater-npmlocal-projectile-npmglobal ()
- "Locate tsserver through project-local or global system settings."
- (or
- (tide--locate-tscompiler (tide--npm-local))
- (tide--locate-tscompiler (tide--project-package))
- (tide--locate-tscompiler (tide--npm-global))
- (tide--locate-tscompiler (tide--npm-global-usrlocal))))
-
-(defun tide-locate-tscompiler-executable ()
- "Locate the typescript compiler executable.
-If TIDE-TSCOMPILER-EXECUTABLE is set by the user use it. Otherwise check in the
-npm local package directory, in the project root as defined by projectile, and
-in the npm global installation."
- (or
- (and tide-tscompiler-executable (expand-file-name tide-tscompiler-executable))
- (tide-tscompiler-locater-npmlocal-projectile-npmglobal)))
-
-(defconst tide--tsserver "tsserver.js"
- "File-name of the file that node executes to start the typescript server.")
-
-(defun tide--locate-tsserver (path)
- "Locate the typescript server in PATH.
-Return a string representing the existing full path or nil."
- (let ((exe (expand-file-name tide--tsserver path)))
- (when (file-exists-p exe) exe)))
-
-(defun tide-tsserver-locater-npmlocal-projectile-npmglobal ()
- "Locate tsserver through project-local or global system settings."
- (or
- (tide--locate-tsserver (tide--npm-local))
- (tide--locate-tsserver (tide--project-package))
- (tide--locate-tsserver (tide--npm-global))
- (tide--locate-tsserver (tide--npm-global-usrlocal))))
-
-(defun tide-locate-tsserver-executable ()
- "Locate the typescript server executable.
-If TIDE-TSSERVER-EXECUTABLE is set by the user use it. Otherwise check in the
-npm local package directory, in the project root as defined by projectile, and
-in the npm global installation. If nothing is found use the bundled version."
- (or
- (and tide-tsserver-executable (expand-file-name tide-tsserver-executable))
- (funcall tide-tsserver-locator-function)
- (expand-file-name tide--tsserver tide-tsserver-directory)))
-
-(defun tide-start-server ()
- (when (tide-current-server)
- (error "Server already exist"))
-
- (message "(%s) Starting tsserver..." (tide-project-name))
- (let* ((default-directory (tide-project-root))
- (process-environment (append tide-tsserver-process-environment process-environment))
- (buf (generate-new-buffer tide-server-buffer-name))
- (tsserverjs (tide-locate-tsserver-executable))
- ;; Use a pipe to communicate with the subprocess. This fixes a hang
- ;; when a >1k message is sent on macOS.
- (process-connection-type nil)
- (node-process-arguments (append tide-node-flags (list tsserverjs) tide-tsserver-flags))
- (process
- (apply #'start-file-process "tsserver" buf tide-node-executable node-process-arguments)))
- (set-process-coding-system process 'utf-8-unix 'utf-8-unix)
- (set-process-filter process #'tide-net-filter)
- (set-process-sentinel process #'tide-net-sentinel)
- (set-process-query-on-exit-flag process nil)
- (with-current-buffer (process-buffer process)
- (buffer-disable-undo))
- (process-put process 'project-name (tide-project-name))
- (process-put process 'project-root default-directory)
- (puthash (tide-project-name) process tide-servers)
- (message "(%s) tsserver server started successfully." (tide-project-name))
- (tide-each-buffer (tide-project-name) #'tide-configure-buffer)))
-
-(defun tide-cleanup-buffer-callbacks ()
- (let ((error-response `(:success ,nil)))
- (maphash
- (lambda (id callback)
- (when (equal (current-buffer) (car callback))
- (funcall (cdr callback) error-response)
- (remhash id tide-response-callbacks)))
- tide-response-callbacks)))
-
-(defun tide-cleanup-project (project-name)
- (tide-each-buffer project-name
- (lambda ()
- (tide-cleanup-buffer-callbacks)))
- (tide-cleanup-project-data project-name))
-
-(defun tide-cleanup-project-data (project-name)
- (remhash project-name tide-servers)
- (remhash project-name tide-tsserver-unsupported-commands)
- (remhash project-name tide-project-configs))
-
-(defvar tide--cleanup-timer nil)
-(defvar tide--cleanup-kinds nil)
-(defun tide-schedule-cleanup (kind)
- (cl-pushnew kind tide--cleanup-kinds)
- (when (and tide-project-cleanup-delay (not tide--cleanup-timer))
- (setq tide--cleanup-timer
- (run-with-idle-timer tide-project-cleanup-delay nil
- #'tide-do-cleanups))))
-(defun tide-schedule-dead-projects-cleanup ()
- (tide-schedule-cleanup 'dead-projects))
-(defun tide-schedule-tmp-file-cleanup ()
- (tide-schedule-cleanup 'tmp-file))
-(defun tide-do-cleanups (&optional interactivep)
- "Perform some cleanups.
-
-This function is used when Emacs is idle (see `tide-project-cleanup-delay'),
-and you can also call it interactively (e.g., if you disable the automatic
-behavior).
-
-Currently, two kinds of cleanups are done:
-* Remove projects that don't have any associated live buffers, and kill their
- tsserver processes.
-* Remove tmp files for buffers that are saved."
- (interactive '(t))
- (let ((kinds (if interactivep '(dead-projects tmp-file) tide--cleanup-kinds))
- (live-projects '())
- (buffers-w/tmp '()))
- (dolist (b (buffer-list))
- (with-current-buffer b
- (when (bound-and-true-p tide-mode)
- (cl-pushnew (tide-project-name) live-projects))
- (when (bound-and-true-p tide-buffer-tmp-file)
- (cl-pushnew b buffers-w/tmp))))
- (setq tide--cleanup-timer nil tide--cleanup-kinds nil)
- (when (memq 'dead-projects kinds)
- (-when-let (to-kill (-difference (hash-table-keys tide-servers) live-projects))
- (message "Cleaning up %d projects..." (length to-kill))
- (dolist (proj to-kill)
- (delete-process (process-buffer (gethash proj tide-servers)))
- (tide-cleanup-project-data proj))))
- (when (and (consp buffers-w/tmp) (memq 'tmp-file kinds))
- (message "Cleaning up %d tmp files..." (length buffers-w/tmp))
- (dolist (b buffers-w/tmp)
- (with-current-buffer b (tide-remove-tmp-file))))
- (unless interactivep
- (sit-for 5)
- (message nil))))
-
-(defun tide-start-server-if-nonexistent ()
- "Start a tsserver instance if there is not one already running."
- (unless (tide-current-server)
- (tide-start-server)))
-
-(defun tide-decode-response-legth ()
- (goto-char (point-min))
- (when (re-search-forward "Content-Length: \\([0-9]+\\)" nil t)
- (string-to-number (match-string 1))))
-
-(defun tide-enough-response-p (length)
- (save-excursion
- (when (search-forward "{" nil t)
- (backward-char 1)
- (>= (- (position-bytes (point-max)) (position-bytes (point))) (1- length)))))
-
-(defun tide-decode-response (process)
- (with-current-buffer (process-buffer process)
- (let ((length (tide-decode-response-legth)))
- (when (and length (tide-enough-response-p length))
- (search-forward "{")
- (backward-char 1)
- (let ((response (if (> length tide-server-max-response-length)
- (let ((seq (when (re-search-forward "\"request_seq\":\"\\([0-9]+\\)\"" nil t)
- (match-string 1))))
- (forward-line 1)
- (and seq
- `(:success :json-false :type "response"
- :message ,tide-max-response-length-error-message
- :request_seq ,seq)))
- (tide-json-read-object))))
- (delete-region (point-min) (point))
- (when response
- (tide-dispatch response)))
- (when (>= (buffer-size) 16)
- (tide-decode-response process))))))
-
-;;; Initialization
-
-(defun tide-file-format-options ()
- (tide-combine-plists
- `(:tabSize ,tab-width :indentSize ,(tide-current-indentsize))
- tide-format-options
- (tide-tsfmt-options)))
-
-(defun tide-tsfmt-options ()
- (let ((config-file (expand-file-name "tsfmt.json" (tide-project-root))))
- (when (file-exists-p config-file)
- (tide-safe-json-read-file config-file))))
-
-(defun tide-current-indentsize ()
- (pcase major-mode
- (`typescript-mode typescript-indent-level)
- (`js2-mode js2-basic-offset)
- (`js-mode js-indent-level)
- (`js3-mode js3-indent-level)
- (`web-mode web-mode-code-indent-offset)
- (`js2-jsx-mode sgml-basic-offset)
- (`rjsx-mode sgml-basic-offset)
- (_ standard-indent)))
-
-(defun tide-command:configure ()
- (tide-send-command
- "configure"
- `(:hostInfo ,(emacs-version) :file ,(tide-buffer-file-name)
- :formatOptions ,(tide-file-format-options) :preferences ,tide-user-preferences)))
-
-(defun tide-command:projectInfo (cb &optional need-file-name-list file)
- (tide-send-command "projectInfo" `(:file ,(or file (tide-buffer-file-name)) :needFileNameList ,need-file-name-list) cb))
-
-(defun tide-command:openfile ()
- (tide-send-command "open"
- (if tide-require-manual-setup
- `(:file
- ,(tide-buffer-file-name)
- :scriptKindName ,tide-default-mode
- :fileContent ,(buffer-string))
- (append `(:file ,(tide-buffer-file-name))
- (let ((extension (upcase (or (file-name-extension (tide-buffer-file-name)) ""))))
- (when (member extension '("TS" "JS" "TSX" "JSX"))
- `(:scriptKindName ,extension)))))))
-
-(defun tide-command:closefile ()
- (tide-send-command "close" `(:file ,(tide-buffer-file-name))))
-
-;;; Jump to definition
-
-(defun tide-command:definition (&optional cb location)
- (let ((location (or location
- `(:file ,(tide-buffer-file-name)
- :line ,(tide-line-number-at-pos)
- :offset ,(tide-current-offset)))))
- (if cb
- (tide-send-command "definition" location cb)
- (tide-send-command-sync "definition" location))))
-
-(defun tide-command:typeDefinition (cb)
- (tide-send-command
- "typeDefinition"
- `(:file ,(tide-buffer-file-name) :line ,(tide-line-number-at-pos) :offset ,(tide-current-offset))
- cb))
-
-(defun tide-jump-to-definition (&optional arg)
- "Jump to the definition of the symbol at point.
-
-If pointed at an abstract member-declaration, will proceed to look for
-implementations. When invoked with a prefix arg, jump to the type definition."
- (interactive "P")
- (let ((cb (lambda (response)
- (tide-on-response-success response
- (-when-let (filespan (car (plist-get response :body)))
- ;; if we're still at the same location...
- ;; maybe we're a abstract member which has implementations?
- (if (and (not arg)
- (tide-filespan-is-current-location-p filespan))
- (tide-jump-to-implementation)
- (tide-jump-to-filespan filespan tide-jump-to-definition-reuse-window)))))))
- (if arg
- (tide-command:typeDefinition cb)
- (tide-command:definition cb))))
-
-(defun tide-filespan-is-current-location-p (filespan)
- (let* ((location (plist-get filespan :start))
- (new-file-name (plist-get filespan :file)))
- (and (string-equal new-file-name (tide-buffer-file-name))
- (equal (tide-location-to-point location) (point)))))
-
-(defun tide-move-to-location (location)
- (let* ((line (plist-get location :line))
- (offset (plist-get location :offset)))
- (save-restriction
- (widen)
- (goto-char (point-min))
- (forward-line (1- line)))
- (unless (and (= offset 0) (= line 0))
- (forward-char (1- offset)))))
-
-(defun tide-location-to-point (location)
- (save-excursion
- (tide-move-to-location location)
- (point)))
-
-(defun tide-recenter-p (filespan &optional recenter-pref)
- (when recenter-pref
- (let ((new-file-name (plist-get filespan :file)))
- (if (string-equal new-file-name (tide-buffer-file-name))
- (tide-recenter-in-same-buffer-p filespan)
- t))))
-
-(defun tide-recenter-in-same-buffer-p (filespan)
- (let* ((newpos (plist-get (plist-get filespan :start) :line))
- (line-diff (abs (- (line-number-at-pos) newpos))))
- (> line-diff (window-body-height))))
-
-(defun tide-jump-to-filespan (filespan &optional reuse-window no-marker)
- (let ((file (plist-get filespan :file))
- (should-recenter-p (tide-recenter-p filespan tide-recenter-after-jump)))
- (unless no-marker
- (xref-push-marker-stack))
- (if reuse-window
- (pop-to-buffer (tide-get-file-buffer file) '((display-buffer-reuse-window display-buffer-same-window)))
- (pop-to-buffer (tide-get-file-buffer file)))
- (tide-move-to-location (plist-get filespan :start))
- (when should-recenter-p
- (recenter))))
-
-(defalias 'tide-jump-back 'pop-tag-mark)
-
-;;; Jump to implementation
-
-(defun tide-jump-to-fallback-not-given ()
- (message "No implementations available."))
-
-(defun tide-command:implementation ()
- (tide-send-command-sync
- "implementation"
- `(:file ,(tide-buffer-file-name) :line ,(tide-line-number-at-pos) :offset ,(tide-current-offset))))
-
-(defun tide-jump-to-implementation-format-item (item)
- (let* ((file-name (plist-get item :file))
- (line (tide-file-span-first-line-text item))
- (file-pos (concat
- (propertize (file-name-nondirectory file-name)
- 'face 'tide-file)
- ":"
- (propertize (number-to-string (tide-plist-get item :start :line))
- 'face 'tide-line-number))))
- (concat
- line
- " "
- file-pos)))
-
-(defun tide-jump-to-implementation ()
- "Jump to a implementation of the symbol at point."
- (interactive)
- (let ((response (tide-command:implementation)))
- (tide-on-response-success response
- (let ((impls (plist-get response :body)))
- (cl-case (length impls)
- ((0) (funcall tide-jump-to-fallback))
- ((1) (tide-jump-to-filespan (car impls)))
- (t (tide-jump-to-filespan
- (tide-select-item-from-list "Select implementation: " impls
- #'tide-jump-to-implementation-format-item
- (tide-can-use-popup-p 'jump-to-implementation)))))))))
-
-;;; Navigate to named member
-
-(defun tide-in-string-p ()
- (nth 3 (syntax-ppss)))
-
-(defun tide-get-symbol-at-point ()
- "Returns the symbol found at point, if not deemed 'noise'.
-Noise can be anything like braces, reserved keywords, etc."
-
- (unless (or (tide-in-string-p)
- (member (face-at-point) '(font-lock-keyword-face)))
- ;; we could have used symbol-at-point here, but that leaves us unable to
- ;; differentiate between a symbol named nil and no symbol at all.
- ;; thing-at-point returns a string OR nil, which means we don't get this problem.
- (let ((symbol (thing-at-point 'symbol)))
- (substring-no-properties (if (equal nil symbol) "" symbol)))))
-
-(defun tide-nav (arg)
- "Search and navigate to named types."
- (interactive "P")
- (let ((completion-ignore-case t)
- (last-completions nil)
- (default (when arg (tide-get-symbol-at-point))))
- (-when-let (completion
- (completing-read-default
- "Search: "
- (completion-table-dynamic
- (lambda (prefix)
- (let ((response (tide-command:navto prefix)))
- (tide-on-response-success response
- (-when-let (navto-items (plist-get response :body))
- (setq navto-items (funcall tide-navto-item-filter navto-items))
- (setq last-completions navto-items)
- (-map (lambda (navto-item) (plist-get navto-item :name))
- navto-items)))))
- t) nil t default))
- (let ((navto-item (-find (lambda (navto-item) (string-equal completion (plist-get navto-item :name)))
- last-completions)))
- (tide-jump-to-filespan navto-item)))))
-
-(defun tide-navto-item-filter-default (navto-items)
- (-filter
- (lambda (navto-item) (member (plist-get navto-item :kind) '("class" "interface" "type" "enum")))
- navto-items))
-
-(defun tide-command:navto (type &optional current-file-only)
- (tide-send-command-sync "navto" `(:file ,(tide-buffer-file-name) :searchValue ,type :maxResultCount 100 :currentFileOnly ,(or current-file-only :json-false))))
-
-;;; Eldoc
-
-(defun tide-annotate-display-part (display-part &optional highlight)
- (let ((text (plist-get display-part :text))
- (face (pcase (plist-get display-part :kind)
- ("aliasName" 'font-lock-type-face)
- ("className" 'font-lock-type-face)
- ("enumName" 'font-lock-type-face)
- ("fieldName" nil)
- ("interfaceName" 'font-lock-type-face)
- ("keyword" 'font-lock-keyword-face)
- ("lineBreak" nil)
- ("numericLiteral" nil)
- ("stringLiteral" 'font-lock-string-face)
- ("localName" 'font-lock-variable-name-face)
- ("methodName" nil)
- ("moduleName" nil)
- ("operator" nil)
- ("parameterName" (and highlight 'eldoc-highlight-function-argument))
- ("propertyName" nil)
- ("punctuation" nil)
- ("space" nil)
- ("text" nil)
- ("typeParameterName" 'font-lock-variable-name-face)
- ("enumMemberName" 'font-lock-constant-face)
- ("functionName" 'font-lock-function-name-face)
- ("regularExpressionLiteral" 'font-lock-string-face))))
- (if face
- (propertize text 'face face)
- text)))
-
-(defun tide-annotate-display-parts (display-parts &optional highlight)
- (tide-join (-map (lambda (part) (tide-annotate-display-part part highlight)) display-parts)))
-
-(defun tide-annotate-signature-parameter (parameter highlight)
- (tide-join
- (-map
- (lambda (part) (tide-annotate-display-part part highlight))
- (plist-get parameter :displayParts))))
-
-(defun tide-annotate-signature (signature selected-arg-index)
- (let ((separator (tide-join (-map #'tide-annotate-display-part (plist-get signature :separatorDisplayParts)))))
- (tide-join
- (-concat
- (-map #'tide-annotate-display-part (plist-get signature :prefixDisplayParts))
- (list
- (mapconcat
- #'identity
- (-map-indexed
- (lambda (i parameter)
- (tide-annotate-signature-parameter parameter (eq i selected-arg-index)))
- (plist-get signature :parameters))
- separator))
- (-map #'tide-annotate-display-part (plist-get signature :suffixDisplayParts))))))
-
-(defun tide-annotate-signatures (body)
- (let ((selected-index (plist-get body :selectedItemIndex))
- (selected-arg-index (plist-get body :argumentIndex)))
- (tide-annotate-signature
- (nth selected-index (plist-get body :items))
- selected-arg-index)))
-
-(defun tide-command:signatureHelp (cb)
- (tide-send-command
- "signatureHelp"
- `(:file ,(tide-buffer-file-name) :line ,(tide-line-number-at-pos) :offset ,(tide-current-offset))
- (tide-on-response-success-callback response (:ignore-empty t)
- (funcall cb (tide-annotate-signatures (plist-get response :body))))))
-
-(defun tide-method-call-p ()
- (or (looking-at "[(,]")
- (and (not (looking-at "\\sw")) (looking-back "[(,]\n?\\s-*" nil))))
-
-(defun tide-doc-text (quickinfo-or-completion-detail)
- (or (plist-get quickinfo-or-completion-detail :displayString) ;; old
- (tide-annotate-display-parts
- (plist-get quickinfo-or-completion-detail :displayParts))))
-
-(defun tide-doc-documentation (quickinfo-or-completion-detail)
- (let ((documentation (plist-get quickinfo-or-completion-detail :documentation)))
- (if (stringp documentation) ;; old
- documentation
- (tide-annotate-display-parts documentation))))
-
-(defun tide-format-jsdoc (name text)
- (setq text (s-trim (or text "")))
- (concat (propertize (concat "@" name) 'face 'font-lock-keyword-face)
- (if (s-contains? "\n" text) "\n" " ")
- text
- "\n"))
-
-(defun tide-doc-jsdoc (quickinfo-or-completion-detail)
- (tide-join
- (-map
- (lambda (tag)
- (tide-format-jsdoc (plist-get tag :name) (plist-get tag :text)))
- (plist-get quickinfo-or-completion-detail :tags))))
-
-(defun tide-construct-documentation (quickinfo-or-completion-detail)
- (when quickinfo-or-completion-detail
- (let* ((display-string (tide-doc-text quickinfo-or-completion-detail))
- (documentation (tide-doc-documentation quickinfo-or-completion-detail))
- (jsdoc (tide-doc-jsdoc quickinfo-or-completion-detail)))
- (when (or (or (not (s-blank? documentation))
- (not (s-blank? jsdoc)))
- tide-always-show-documentation)
- (tide-join
- (-concat (list display-string "\n\n")
- (if (not (s-blank? documentation)) (list documentation "\n\n") '())
- (list jsdoc)))))))
-
-(defun tide-command:quickinfo-old (cb)
- (tide-send-command "quickinfo"
- `(:file ,(tide-buffer-file-name)
- :line ,(tide-line-number-at-pos) :offset ,(tide-current-offset)) cb))
-
-(defun tide-command:quickinfo-full (cb)
- (tide-send-command "quickinfo-full"
- `(:file ,(tide-buffer-file-name)
- :line ,(tide-line-number-at-pos) :offset ,(tide-current-offset))
- cb))
-
-(defun tide-command:quickinfo (cb)
- (tide-fallback-if-not-supported "quickinfo-full" tide-command:quickinfo-full tide-command:quickinfo-old cb))
-
-
-(defun tide-eldoc-function ()
- (unless (member last-command '(next-error previous-error))
- (if (tide-method-call-p)
- (tide-command:signatureHelp #'tide-eldoc-maybe-show)
- (when (looking-at "\\s_\\|\\sw")
- (tide-command:quickinfo
- (tide-on-response-success-callback response (:ignore-empty t)
- (tide-eldoc-maybe-show (tide-doc-text (plist-get response :body))))))))
- nil)
-
-(defun tide-eldoc-display-message-p()
- (if (fboundp 'eldoc-display-message-no-interference-p)
- (eldoc-display-message-no-interference-p)
- (eldoc-display-message-p)))
-
-;;; Copied from eldoc.el
-(defun tide-eldoc-maybe-show (text)
- (with-demoted-errors "eldoc error: %s"
- (and (or (tide-eldoc-display-message-p)
- ;; Erase the last message if we won't display a new one.
- (when eldoc-last-message
- (eldoc-message nil)
- nil))
- (eldoc-message text))))
-
-(defun tide-documentation-at-point ()
- "Show documentation of the symbol at point."
- (interactive)
- (tide-command:quickinfo
- (tide-on-response-success-callback response
- (-if-let (body (tide-construct-documentation (plist-get response :body)))
- (tide-display-help-buffer "documentation" body)
- (message "No documentation available.")))))
-
-;;; Buffer Sync
-
-(defun tide-remove-tmp-file ()
- (when tide-buffer-tmp-file
- (delete-file tide-buffer-tmp-file)
- (setq tide-buffer-tmp-file nil)))
-
-(defun tide-command:reloadfile ()
- (tide-send-command "reload" `(:file ,(tide-buffer-file-name) :tmpfile ,(tide-buffer-file-name))))
-
-(defun tide-handle-change (_beg _end _len)
- (setq tide-buffer-dirty t))
-
-(defun tide-sync-buffer-contents ()
- ;; The real file that backs a buffer could be changed in various
- ;; ways, one common example is the rename operation. Ensure that we
- ;; send the open command for the new file before using it as an
- ;; argument for any other command.
- (unless (string-equal tide-active-buffer-file-name (tide-buffer-file-name))
- (tide-configure-buffer))
- (when (and tide-buffer-tmp-file (not (buffer-modified-p)))
- (tide-schedule-tmp-file-cleanup))
- (when tide-buffer-dirty
- (setq tide-buffer-dirty nil)
- (unless tide-buffer-tmp-file
- (setq tide-buffer-tmp-file (make-temp-file "tide")))
- (save-restriction
- (widen)
- (write-region (point-min) (point-max) tide-buffer-tmp-file nil 'no-message))
- (tide-send-command "reload" `(:file ,(tide-buffer-file-name) :tmpfile ,tide-buffer-tmp-file))))
-
-;;; Code-fixes
-
-(defun tide-apply-code-edits (file-code-edits)
- (save-excursion
- (dolist (file-code-edit file-code-edits)
- (let ((file (plist-get file-code-edit :fileName)))
- (with-current-buffer (tide-get-file-buffer file t)
- (tide-format-regions (tide-apply-edits (plist-get file-code-edit :textChanges)))
- ;; Saving won't work if the current buffer is temporary or an indirect
- ;; buffer
- (when (and tide-save-buffer-after-code-edit (equal buffer-file-name file))
- (basic-save-buffer))
- (run-hooks 'tide-post-code-edit-hook))))))
-
-(defun tide-get-flycheck-errors-ids-at-point ()
- (-map #'flycheck-error-id (flycheck-overlay-errors-at (point))))
-
-(defun tide-command:getCodeFixes ()
- (tide-send-command-sync
- "getCodeFixes"
- `(:file ,(tide-buffer-file-name)
- :startLine ,(tide-line-number-at-pos) :startOffset ,(tide-current-offset)
- :endLine ,(tide-line-number-at-pos) :endOffset ,(+ 1 (tide-current-offset))
- :errorCodes ,(vconcat (tide-get-flycheck-errors-ids-at-point)))))
-
-(defun tide-command:getCombinedCodeFix (fixId)
- (tide-send-command-sync "getCombinedCodeFix"
- `(:scope (:type "file" :args (:file ,(tide-buffer-file-name))) :fixId ,fixId)))
-
-(defun tide-get-fix-description (fix)
- (plist-get fix :description))
-
-(defun tide-apply-codefix (fix)
- "Apply a single `FIX', which may apply to several files."
- (tide-apply-code-edits (plist-get fix :changes)))
-
-(defun tide-apply-codefix-for-all-in-file (fix)
- (tide-apply-codefix fix)
- (-when-let* ((fix-id (plist-get fix :fixId))
- (response (tide-command:getCombinedCodeFix fix-id)))
- (tide-on-response-success response
- (tide-apply-codefix (plist-get response :body)))))
-
-(defun tide-apply-codefixes (fixes codefix-fn)
- (cond ((= 0 (length fixes)) (message "No code-fixes available."))
- ((= 1 (length fixes)) (funcall codefix-fn (car fixes)))
- (t (funcall
- codefix-fn
- (tide-select-item-from-list "Select fix: " fixes
- #'tide-get-fix-description (tide-can-use-popup-p 'code-fix))))))
-
-(defun tide-code-fix (codefix-fn)
- (unless (tide-get-flycheck-errors-ids-at-point)
- (error "No errors available at current point."))
- (let ((response (tide-command:getCodeFixes)))
- (tide-on-response-success response
- (let ((fixes (plist-get response :body)))
- (tide-apply-codefixes fixes codefix-fn)))))
-
-(defun tide-fix (&optional arg)
- "Apply code fix for the error at point.
-
-When invoked with a prefix arg, apply code fix for all the errors
-in the file that are similar to the error at point."
- (interactive "P")
- (if arg
- (tide-code-fix #'tide-apply-codefix-for-all-in-file)
- (tide-code-fix #'tide-apply-codefix)))
-
-;;; Organize Imports
-
-(defun tide-command:organizeImports ()
- (tide-send-command-sync "organizeImports" `(:scope (:type "file" :args (:file ,(tide-buffer-file-name))))))
-
-(defun tide-organize-imports ()
- "Organize imports in the file."
- (interactive)
- (let ((response (tide-command:organizeImports)))
- (tide-on-response-success response (:min-version "2.8")
- (-when-let (changes (plist-get response :body))
- (tide-apply-code-edits changes)))))
-
-;;; Refactor
-
-(defun tide-location-or-range ()
- (if (use-region-p)
- (let ((start (region-beginning))
- (end (region-end)))
- `(:startLine ,(tide-line-number-at-pos start) :startOffset ,(tide-offset start)
- :endLine ,(tide-line-number-at-pos end) :endOffset ,(tide-offset end)))
- `(:line ,(tide-line-number-at-pos) :offset ,(tide-current-offset))))
-
-(defun tide-command:getEditsForRefactor (refactor action)
- (tide-send-command-sync
- "getEditsForRefactor"
- (append `(:refactor ,refactor :action ,action :file ,(tide-buffer-file-name))
- (tide-location-or-range))))
-
-(defun tide-command:getApplicableRefactors ()
- (tide-send-command-sync
- "getApplicableRefactors"
- (append `(:file ,(tide-buffer-file-name)) (tide-location-or-range))))
-
-(defun tide-get-refactor-description (refactor)
- (plist-get refactor :description))
-
-(defun tide-select-refactor (applicable-refactor-infos)
- (let ((available-refactors
- (-mapcat
- (lambda (applicable-refactor-info)
- (-map (lambda (refactor-action-info)
- `(:action ,(plist-get refactor-action-info :name)
- :refactor ,(plist-get applicable-refactor-info :name)
- :inlineable ,(plist-get applicable-refactor-info :inlineable)
- :description ,(plist-get refactor-action-info :description)))
- (plist-get applicable-refactor-info :actions)))
- applicable-refactor-infos)))
- (tide-select-item-from-list "Select refactor: " available-refactors
- #'tide-get-refactor-description (tide-can-use-popup-p 'refactor))))
-
-(defun tide-apply-refactor (selected)
- (let ((response (tide-command:getEditsForRefactor (plist-get selected :refactor) (plist-get selected :action))))
- (tide-on-response-success response
- (progn
- (deactivate-mark)
- (tide-apply-code-edits (tide-plist-get response :body :edits))
- (-when-let (rename-location (tide-plist-get response :body :renameLocation))
- (with-current-buffer (tide-get-file-buffer (tide-plist-get response :body :renameFilename))
- (tide-move-to-location rename-location)
- (when (tide-can-rename-symbol-p)
- (tide-rename-symbol))))))))
-
-(defun tide-refactor ()
- "Refactor code at point or current region"
- (interactive)
- (let ((response (tide-command:getApplicableRefactors)))
- (tide-on-response-success response (:min-version "2.4")
- (-if-let (body (plist-get response :body))
- (tide-apply-refactor
- (tide-select-refactor body))
- (message "No refactors available.")))))
-
-;;; Disable tslint warnings
-
-(defconst tide-tslint-disable-next-line-regexp
- "\\s *//\\s *tslint\\s *:\\s *disable-next-line\\s *:\\(.*\\)"
- "Regexp matching a tslint flag disabling rules on the next line.")
-
-(defun tide-add-tslint-disable-next-line ()
- "Add a tslint flag to disable rules generating errors at point.
-
-This function adds or modifies a flag of this form to the
-previous line:
-
- // tslint:disable-next-line:[rule1] [rule2] [...]
-
-The line will be indented according to the current indentation
-settings. This function generates rule1, rule2 to cover all the
-errors present at point.
-
-If the previous line does not already contain a disable-next-line
-flag, a new line is added to hold the new flag. If the previous
-line already contains a disable-next-line flag, the rule is added
-to the flag. Note that this function does not preserve the
-formatting of the already existing flag. The resulting flag will
-always be formatted as described above."
- (interactive)
- (let ((error-ids (delq nil (tide-get-flycheck-errors-ids-at-point)))
- (start (point)))
- (when error-ids
- (save-excursion
- (if (and (eq 0 (forward-line -1))
- (looking-at tide-tslint-disable-next-line-regexp))
- ;; We'll update the old flag.
- (let ((old-list (split-string (match-string 1))))
- (delete-region (point) (point-at-eol))
- (setq error-ids (append old-list error-ids)))
- ;; We'll create a new flag.
- (goto-char start)
- (beginning-of-line)
- (open-line 1))
- (insert "// tslint:disable-next-line:"
- (string-join error-ids " "))
- (typescript-indent-line)))))
-
-;;; Disable Eslint Warnings
-
-(defconst tide-eslint-disable-next-line-regexp
- "\\s *//\\s *eslint-disable-next-line\\s *\\(.*\\)")
-
-(defun tide-add-eslint-disable-next-line ()
- "Add a eslint flag to disable rules generating errors at point.
-
-This function adds or modifies a flag of this form to the
-previous line:
-
- // eslint-disable-next-line [rule1], [rule2], [...]
-
-The line will be indented according to the current indentation
-settings. This function generates rule1, rule2 to cover all the
-errors present at point.
-
-If the previous line does not already contain a disable-next-line
-flag, a new line is added to hold the new flag. If the previous
-line already contains a disable-next-line flag, the rule is added
-to the flag. Note that this function does not preserve the
-formatting of the already existing flag. The resulting flag will
-always be formatted as described above."
- (interactive)
- (let ((error-ids (delq nil (tide-get-flycheck-errors-ids-at-point)))
- (start (point)))
- (when error-ids
- (save-excursion
- (if (and (eq 0 (forward-line -1))
- (looking-at tide-eslint-disable-next-line-regexp))
- ;; We'll update the old flag.
- (let ((old-list (split-string (match-string 1) "\,[\s]+")))
- (delete-region (point) (point-at-eol))
- (setq error-ids (append old-list error-ids)))
- ;; We'll create a new flag.
- (goto-char start)
- (beginning-of-line)
- (open-line 1))
- (insert "// eslint-disable-next-line "
- (string-join error-ids ", "))
- (typescript-indent-line)))))
-
-;;; Auto completion
-
-(defun tide-completion-annotation (name)
- (-if-let (meta (and tide-completion-detailed (tide-completion-meta name)))
- ;; Get everything before the first newline, if any, because company-mode
- ;; wants single-line annotations.
- (car (split-string meta "\n"))
- (if tide-completion-show-source
- (tide-completion-append-source (tide-completion-annotation-trans-mark name) name nil)
- (tide-completion-annotation-trans-mark name))))
-
-(defun tide-completion-annotation-trans-mark (name)
- (pcase (plist-get (get-text-property 0 'completion name) :kind)
- ("keyword" " k")
- ("module" " M")
- ("class" " C")
- ("interface" " I")
- ("type" " T")
- ("enum" " E")
- ("var" " v")
- ("local var" " v")
- ("function" " ƒ")
- ("local function" " ƒ")
- ("method" " m")
- ("getter" " m")
- ("setter" " m")
- ("property" " p")
- ("constructor" " c")
- ("call" " m")
- ("index" " i")
- ("construct" " m")
- ("parameter" " p")
- ("type parameter" " T")
- ("primitive type" " T")
- ("label" " l")
- ("alias" " A")
- ("const" " c")
- ("let" " l")
- (_ nil)))
-
-(defun tide-completion-kind (name)
- (pcase (plist-get (get-text-property 0 'completion name) :kind)
- ((or "primitive type" "keyword") 'keyword)
- ((or "const" "let" "var" "local var" "alias" "parameter") 'variable)
- ((or "property" "getter" "setter") 'field)
- ((or "function" "local function") 'function)
- ((or "method" "construct" "call" "index") 'method)
- ("enum" 'enum)
- ("enum member" 'enum-member)
- ((or "module" "external module name") 'module)
- ((or "class" "type") 'class)
- ("interface" 'interface)
- ("warning" 'text)
- ("script" 'file)
- ("directory" 'folder)
- ("string" 'constant)
- (_ 'property)))
-
-(defun tide-completion-rank (completion)
- "Get the sorting order of a COMPLETION candidate."
- (or
- (-elem-index
- (plist-get completion :kind)
- '("parameter"
- "local function"
- "local var"
- "let"
- "var"
- "const"
- "function"
- "class"
- "method"
- "getter"
- "setter"
- ))
- 100))
-
-(defun tide-compose-comparators (cmp1 cmp2)
- "A helper function that composes two comparators CMP1 and CMP2."
- (lambda (a b)
- (cond ((funcall cmp1 a b) t)
- ((funcall cmp1 b a) nil)
- (t (funcall cmp2 a b)))))
-
-(defun tide-compare-completions-basic (completion-a completion-b)
- "Compare COMPLETION-A and COMPLETION-B based on their `sortText' property.
-This function is used for the basic completions sorting."
- (let ((sort-text-a (plist-get completion-a :sortText))
- (sort-text-b (plist-get completion-b :sortText)))
- (string< sort-text-a sort-text-b)))
-
-(defun tide-compare-completions-by-kind (completion-a completion-b)
- "Compare COMPLETION-A and COMPLETION-B based on their kind."
- (let ((modifier-a (plist-get completion-a :kindModifiers))
- (modifier-b (plist-get completion-b :kindModifiers)))
- (if (string-equal modifier-a modifier-b)
- (< (tide-completion-rank completion-a) (tide-completion-rank completion-b))
- ;; Rank declarations lower than variables
- (string-equal modifier-b "declare"))))
-
-(defun tide-completion-prefix ()
- (if (and (tide-in-string-p)
- (looking-back
- (rx (or (and "import" (1+ space) (or ?\" ?') (0+ (not (any ?\" ?'))))
- (and "from" (1+ space) (or ?\" ?') (0+ (not (any ?\" ?'))))
- (and "import(" (or ?\" ?') (0+ (not (any ?\" ?'))))
- (and "require(" (or ?\" ?') (0+ (not (any ?\" ?'))))))
- nil))
- (cons (company-grab (rx (or ?/ ?\" ?') (group (0+ (not (any ?\" ?'))))) 1) t)
- (company-grab-symbol-cons "\\." 1)))
-
-(defun tide-member-completion-p (prefix)
- (save-excursion
- (backward-char (length prefix))
- (and (> (point) (point-min))
- (equal (string (char-before (point))) "."))))
-
-(defun tide-completion-filter-candidates (completions prefix)
- (-filter (lambda (completion)
- (and
- (if tide-completion-fuzzy
- (let ((case-fold-search tide-completion-ignore-case))
- (string-match-p (regexp-quote prefix) (plist-get completion :name)))
- (string-prefix-p prefix (plist-get completion :name) tide-completion-ignore-case))
- (or (not tide-filter-out-warning-completions)
- (not (equal (plist-get completion :kind) "warning")))))
- completions))
-
-(defun tide-annotate-completions (completions prefix file-location)
- (-map
- (lambda (completion)
- (let ((name (plist-get completion :name)))
- (put-text-property 0 1 'file-location file-location name)
- (put-text-property 0 1 'completion completion name)
- (put-text-property 0 1 'prefix prefix name)
- name))
- (let ((filtered (tide-completion-filter-candidates completions prefix)))
- (let ((completions-comparator
- (if tide-sort-completions-by-kind
- (tide-compose-comparators 'tide-compare-completions-basic
- 'tide-compare-completions-by-kind)
- 'tide-compare-completions-basic)))
- (-sort completions-comparator filtered)))))
-
-(defun tide-command:completions (prefix cb)
- (let ((file-location
- `(:file ,(tide-buffer-file-name)
- :line ,(tide-line-number-at-pos) :offset ,(- (tide-current-offset) (length prefix))
- :includeExternalModuleExports ,tide-completion-enable-autoimport-suggestions
- :includeInsertTextCompletions t)))
- (when (and (not (tide-in-string-p)) (not (tide-member-completion-p prefix)))
- (setq file-location (-concat file-location `(:prefix ,prefix))))
- (tide-send-command
- "completions"
- file-location
- (lambda (response)
- (funcall
- cb
- (when (tide-response-success-p response)
- (tide-annotate-completions (plist-get response :body) prefix file-location)))))))
-
-(defun tide-command:completionEntryDetails (name)
- (let* ((source (plist-get (get-text-property 0 'completion name) :source))
- (entry-names (if source
- `(:entryNames [(:name ,name :source ,source)])
- `(:entryNames [,name])))
- (arguments (-concat (get-text-property 0 'file-location name)
- entry-names)))
- (-when-let (response (tide-send-command-sync "completionEntryDetails" arguments))
- (when (tide-response-success-p response)
- response))))
-
-(defun tide-completion-entry-details (name)
- (-if-let (detail-response (get-text-property 0 'completion-detail name))
- detail-response
- (let ((detail-response (tide-command:completionEntryDetails name)))
- (put-text-property 0 1 'completion-detail detail-response name)
- detail-response)))
-
-(defun tide-completion-meta (name)
- (-when-let* ((response (tide-completion-entry-details name))
- (detail (car (plist-get response :body))))
- (tide-completion-append-source (tide-doc-text detail) name t)))
-
-(defun tide-completion-append-source (text name detailed)
- (if detailed
- (-if-let* ((response (tide-completion-entry-details name))
- (detail (car (plist-get response :body)))
- (raw-source (plist-get detail :source)))
- (tide-join (list text " " (tide-normalize-source (tide-annotate-display-parts raw-source))))
- text)
- (-if-let* ((completion (get-text-property 0 'completion name))
- (raw-source (plist-get completion :source)))
- (tide-join (list text " " (tide-normalize-source raw-source)))
- text)))
-
-(defun tide-normalize-source (source)
- "Normalize tsserver returned source:
-
-1. Transform to relative path
-2. Cleanup path components before last `/node_modules/`
-3. Cleanup `/index` if it is the last path component
-4. Transform `@types/namespace__pkgname` to `@namespace/pkgname`"
- (--> source
-
- (if (file-name-absolute-p it)
- (file-relative-name it (buffer-file-name))
- it)
-
- (if (s-contains? "/node_modules/" it)
- (->> it
- (s-split "/node_modules/")
- (-last-item))
- it)
-
- (s-chop-suffix "/index" it)
-
- (if (s-starts-with? "@types/" it)
- (-as-> (s-chop-prefix "@types/" it) itt
- (if (s-contains? "__" itt)
- (->> itt
- (s-replace "__" "/")
- (s-concat "@"))
- itt))
- it)))
-
-(defun tide-completion-doc-buffer (name)
- (-when-let* ((response (tide-completion-entry-details name))
- (detail (car (plist-get response :body))))
- (tide-make-help-buffer "documentation" (tide-construct-documentation detail))))
-
-(defun tide-post-completion (name)
- (let ((completion (get-text-property 0 'completion name))
- (prefix (get-text-property 0 'prefix name)))
-
- (-when-let (insert-text (plist-get completion :insertText))
- (when (looking-back (rx-to-string name) nil)
- (backward-delete-char (length name))
- (-if-let (span (plist-get completion :replacementSpan))
- (progn
- (when (string-prefix-p prefix (plist-get completion :name) tide-completion-ignore-case)
- (insert prefix)) ;; tsserver assumes the prefix text is already inserted for non-fuzzy completion.
- (tide-apply-edit (tide-combine-plists span `(:newText ,insert-text))))
- (insert insert-text))))
-
- (-when-let* ((has-action (plist-get completion :hasAction))
- (response (tide-completion-entry-details name))
- (detail (car (plist-get response :body)))
- (fixes (plist-get detail :codeActions)))
- (tide-apply-codefixes fixes #'tide-apply-codefix))))
-
-;;;###autoload
-(defun company-tide (command &optional arg &rest ignored)
- (interactive (list 'interactive))
- (cl-case command
- ((interactive) (company-begin-backend 'company-tide))
- ((prefix) (and
- (bound-and-true-p tide-mode)
- (-any-p #'derived-mode-p tide-supported-modes)
- (tide-current-server)
- (not (nth 4 (syntax-ppss)))
- (or (tide-completion-prefix) 'stop)))
- ((candidates) (cons :async (lambda (cb) (tide-command:completions arg cb))))
- ((sorted) t)
- ((no-cache) tide-completion-fuzzy)
- ((ignore-case) tide-completion-ignore-case)
- ((meta) (tide-completion-meta arg))
- ((match)
- (let* ((completion (get-text-property 0 'completion arg))
- (prefix (get-text-property 0 'prefix arg))
- (start (if tide-completion-fuzzy
- (let ((case-fold-search tide-completion-ignore-case))
- (string-match-p (regexp-quote prefix) (plist-get completion :name)))
- 0)))
- `((,start . ,(+ start (length prefix))))))
- ((annotation) (tide-completion-annotation arg))
- ((kind) (tide-completion-kind arg))
- ((doc-buffer) (tide-completion-doc-buffer arg))
- ((post-completion) (tide-post-completion arg))
- ((pre-render) (let ((name arg)
- (annotation-p (car ignored)))
- (if (and (not annotation-p)
- (s-contains? "deprecated"
- (or (plist-get (get-text-property 0 'completion name) :kindModifiers)
- "")))
- (propertize name 'face '(:strike-through t))
- name)))))
-
-(with-eval-after-load 'company
- (when tide-completion-setup-company-backend
- (cl-pushnew 'company-tide company-backends)))
-
-;;; References
-
-(defun tide--next-reference (pos arg &optional cyclep)
- "Move to the ARG-th next reference from pos, negative values go to previous
-ones. Cycle around if CYCLEP is non-nil."
- (let* ((nextp (>= arg 0))
- (next (if nextp #'next-single-property-change #'previous-single-property-change))
- (n (* 2 (abs arg))))
- (when (get-text-property pos 'tide-reference)
- (setq pos (or (funcall next pos 'tide-reference) pos)))
- (when (or nextp (and (> pos (point-min))
- (get-text-property (1- pos) 'tide-reference)))
- (setq n (1- n)))
- (dotimes (_i n)
- (setq pos (funcall next pos 'tide-reference))
- (unless pos
- (unless cyclep
- (error "Moved %s reference" (if nextp "past last" "before first")))
- (setq pos (funcall next (if nextp (point-min) (point-max))
- 'tide-reference))))
- (goto-char pos)))
-
-(defun tide-next-reference-function (n &optional reset)
- "Override for `next-error-function' for use in tide-reference-mode buffers."
- (interactive "p")
- (-when-let (buffer (get-buffer "*tide-references*"))
- (with-current-buffer buffer
- (when reset (goto-char (point-min)))
- (tide--next-reference (point) (or n 1))
- (-when-let (window (get-buffer-window buffer))
- ;; actually move to the point if the refs are shown
- (set-window-point window (point)))
- (tide-goto-reference))))
-
-(defun tide-find-next-reference (pos arg)
- "Move to next reference."
- (interactive "d\np")
- (tide--next-reference pos arg))
-(defun tide-cycle-next-reference (pos arg)
- "Move to next reference, cycling back when reaching the last.
-Move back when used from a shifted key binding."
- (interactive "d\np")
- (tide--next-reference pos arg t))
-(defun tide-find-previous-reference (pos arg)
- "Move back to previous reference."
- (interactive "d\np")
- (tide--next-reference pos (- arg)))
-(defun tide-cycle-previous-reference (pos arg)
- (interactive "d\np")
- (tide--next-reference pos (- arg) t))
-
-(defun tide-goto-reference ()
- "Jump to reference location in the file."
- (interactive)
- (-when-let (reference (get-text-property (point) 'tide-reference))
- (tide-jump-to-filespan reference nil t)))
-
-(defun tide-goto-line-reference ()
- "Jump to the corresponding location in the referenced file."
- (interactive)
- (-when-let* ((ref (get-text-property (point) 'tide-line-reference))
- (col (- (point) (line-beginning-position))))
- (tide-jump-to-filespan ref nil t)
- (goto-char (+ (line-beginning-position) col))))
-
-(defvar tide-references-mode-map
- (let ((map (make-sparse-keymap)))
- (define-key map (kbd "n") #'tide-find-next-reference)
- (define-key map (kbd "p") #'tide-find-previous-reference)
- (define-key map (kbd "TAB") #'tide-cycle-next-reference)
- (define-key map (kbd "<backtab>") #'tide-cycle-previous-reference)
- (define-key map (kbd "RET") #'tide-goto-line-reference)
- (define-key map [mouse-1] #'tide-goto-reference)
- ;; taken from grep.el
- (define-key map (kbd "SPC") #'scroll-up-command)
- (define-key map (kbd "S-SPC") #'scroll-down-command)
- (define-key map (kbd "DEL") #'scroll-down-command)
- map))
-
-(define-derived-mode tide-references-mode special-mode "tide-references"
- "Major mode for tide references list.
-
-\\{tide-references-mode-map}"
- (setq next-error-function #'tide-next-reference-function))
-
-(defun tide-command:references (&optional location)
- (let ((location (or location
- `(:file ,(tide-buffer-file-name)
- :line ,(tide-line-number-at-pos)
- :offset ,(tide-current-offset)))))
- (tide-send-command-sync
- "references"
- location)))
-
-(defun tide-insert-references (references)
- "Create a buffer with the given REFERENCES.
-
-Assumes references are grouped by file name and sorted by line
-number."
- (let* ((buffer (get-buffer-create "*tide-references*"))
- (inhibit-read-only t)
- (width tab-width)
- (project-root (tide-project-root))
- (last-file-name nil)
- (prefix-len (length (number-to-string
- (--reduce (max acc (tide-plist-get it :start :line))
- (cons 0 references)))))
- (linenum-fmt (format "%%%dd" prefix-len))
- (wrap-prefix (make-string prefix-len ?\ )))
- (with-current-buffer buffer
- (erase-buffer)
- (tide-references-mode)
- (setq-local tab-width width)
- (while references
- (let* ((reference (car references))
- (full-file-name (plist-get reference :file))
- (file-name (file-relative-name full-file-name project-root))
- (line-number (tide-plist-get reference :start :line))
- (line-text (concat (plist-get reference :lineText) "\n"))
- (line-prefix (concat (propertize (format linenum-fmt line-number)
- 'face 'tide-line-number)
- ": ")))
- ;; file
- (unless (equal last-file-name file-name)
- (setq last-file-name file-name)
- (insert (propertize "\n" 'line-prefix (propertize file-name 'face 'tide-file))))
- ;; line text
- (while (and references
- (equal full-file-name (plist-get (car references) :file))
- (equal line-number (tide-plist-get (car references) :start :line)))
- (let* ((reference (pop references))
- (start (1- (tide-plist-get reference :start :offset)))
- (end (1- (tide-plist-get reference :end :offset))))
- (dolist (p `((tide-reference ,reference)
- (face tide-match)
- (mouse-face highlight)
- (help-echo "mouse-1: Visit the reference.")))
- (put-text-property start end (car p) (cadr p) line-text))))
- (insert (propertize line-text 'line-prefix line-prefix 'wrap-prefix wrap-prefix
- 'tide-line-reference reference))))
- (goto-char (point-min))
- (forward-line 1)
- (set-buffer-modified-p nil)
- (current-buffer))))
-
-(defun tide-is-identical-reference (original second)
- (and (equal (plist-get original :file) (plist-get second :file))
- (eq (tide-plist-get original :start :line) (tide-plist-get second :start :line))))
-(defun tide-find-single-usage (references)
- (let ((definition nil)
- (usage nil)
- (multiple nil))
- (-each references
- #'(lambda (reference)
- (if (eq t (plist-get reference :isDefinition))
- (if (or (eq definition nil) (tide-is-identical-reference definition reference))
- (setq definition reference)
- (setq multiple t))
- (if (or (eq usage nil) (tide-is-identical-reference usage reference))
- (setq usage reference)
- (setq multiple t)))))
- (and (not multiple) definition usage)))
-
-(defun tide-references ()
- "List all references to the symbol at point."
- (interactive)
- (let ((response (tide-command:references)))
- (tide-on-response-success response
- (let ((references (tide-plist-get response :body :refs)))
- (-if-let (usage (tide-find-single-usage references))
- (progn
- (message "This is the only usage.")
- (tide-jump-to-filespan usage nil t))
- (display-buffer (tide-insert-references references)))))))
-
-
-;;; Imenu
-
-(defun tide-build-flat-imenu-index (navtree &optional parent)
- (let* ((child-items (plist-get navtree :childItems))
- (text (plist-get navtree :text))
- (new-text (if parent (concat parent imenu-level-separator text) text))
- (node (cons (concat new-text " " (propertize (plist-get navtree :kind) 'face 'tide-imenu-type-face))
- (tide-span-to-position (plist-get (car (plist-get navtree :spans)) :start)))))
- (if child-items
- (-concat (list node) (-flatten (-map (lambda (i) (tide-build-flat-imenu-index i new-text)) child-items)))
- (list node))))
-
-(defun tide-build-imenu-index (navtree)
- (let* ((child-items (plist-get navtree :childItems))
- (text (plist-get navtree :text))
- (node (cons (concat text " " (propertize (plist-get navtree :kind) 'face 'tide-imenu-type-face))
- (tide-span-to-position (plist-get (car (plist-get navtree :spans)) :start)))))
- (if child-items
- (cons text
- (-concat (list node)
- (-map #'tide-build-imenu-index child-items)))
- node)))
-
-(defun tide-command:navbar ()
- (tide-send-command-sync "navtree" `(:file ,(tide-buffer-file-name))))
-
-(defun tide-imenu-index ()
- (let ((response (tide-command:navbar)))
- (tide-on-response-success response
- (let ((children (tide-plist-get response :body :childItems)))
- (if tide-imenu-flatten
- (-flatten (-map #'tide-build-flat-imenu-index children))
- (mapcar #'tide-build-imenu-index children))))))
-
-;;; Rename
-
-(defun tide-command:rename ()
- (tide-send-command-sync "rename"
- `(:file ,(tide-buffer-file-name)
- :line ,(tide-line-number-at-pos) :offset ,(tide-current-offset))))
-
-(defun tide-rename-symbol-at-location (location new-symbol)
- (let ((file (plist-get location :file)))
- (save-excursion
- (with-current-buffer (tide-get-file-buffer file)
- (-each
- (-map (lambda (filespan)
- (tide-move-to-location (plist-get filespan :start))
- (cons (point-marker) filespan))
- (plist-get location :locs))
- (lambda (pointer)
- (let* ((marker (car pointer))
- (filespan (cdr pointer)))
- (goto-char marker)
- (delete-char (- (tide-plist-get filespan :end :offset) (tide-plist-get filespan :start :offset)))
- (insert new-symbol))))
- ;; Saving won't work if the current buffer is temporary or an indirect
- ;; buffer
- (when (equal buffer-file-name file)
- (basic-save-buffer))
- (length (plist-get location :locs))))))
-
-(defun tide-read-new-symbol (old-symbol)
- (let ((new-symbol (read-from-minibuffer (format "Rename %s to: " old-symbol) old-symbol)))
- (if (string-match-p "\\`[ \t\n\r]*\\'" new-symbol)
- (error "Invalid name")
- new-symbol)))
-
-(defun tide-can-rename-symbol-p ()
- (let ((response (tide-command:rename)))
- (and
- (tide-response-success-p response)
- (eq (tide-plist-get response :body :info :canRename) t))))
-
-(defun tide-rename-symbol ()
- "Rename symbol at point."
- (interactive)
- (let ((response (tide-command:rename)))
- (tide-on-response-success response
- (if (eq (tide-plist-get response :body :info :canRename) :json-false)
- (message "%s" (tide-plist-get response :body :info :localizedErrorMessage))
- (let* ((old-symbol (tide-plist-get response :body :info :displayName))
- (new-symbol (tide-read-new-symbol old-symbol))
- (locs (tide-plist-get response :body :locs))
- (count 0)
- (current-file-p
- (lambda (loc)
- (file-equal-p (expand-file-name (tide-buffer-file-name))
- (plist-get loc :file)))))
-
- ;; Saving current file will trigger a compilation
- ;; check. So make sure all the other files are saved
- ;; before saving current file.
-
- (-each (nconc (-reject current-file-p locs)
- (-select current-file-p locs))
- (lambda (loc)
- (cl-incf count (tide-rename-symbol-at-location loc new-symbol))))
-
- (message "Renamed %d occurrences." count))))))
-
-(defun tide-command:getEditsForFileRename (old new)
- (tide-send-command-sync "getEditsForFileRename" `(:oldFilePath ,old :newFilePath ,new :file ,old)))
-
-(defun tide-do-rename-file (old new edits)
- (let* ((code-edit-for-old-p (lambda (code-edit) (string= (plist-get code-edit :fileName) old)))
- (before-rename-edits (-select code-edit-for-old-p edits))
- (after-rename-edits (-reject code-edit-for-old-p edits)))
- (tide-apply-code-edits before-rename-edits)
- (tide-cleanup-buffer)
- (mkdir (file-name-directory new) t)
- (rename-file old new)
- (rename-buffer (file-name-nondirectory new) t)
- (set-visited-file-name new)
- (set-buffer-modified-p nil)
- (tide-apply-code-edits after-rename-edits)
- (revert-buffer t t t)))
-
-(defun tide-rename-file ()
- "Rename current file and all it's references in other files."
- (interactive)
- (let* ((name (buffer-name))
- (old (tide-buffer-file-name))
- (basename (file-name-nondirectory old)))
- (unless (and old (file-exists-p old))
- (error "Buffer '%s' is not visiting a file." name))
- (let ((new (read-file-name "New name: " (file-name-directory old) basename nil basename)))
- (when (get-file-buffer new)
- (error "A buffer named '%s' already exists." new))
- (when (file-exists-p new)
- (error "A file named '%s' already exists." new))
- (let* ((old (expand-file-name old))
- (new (expand-file-name new))
- (response (tide-command:getEditsForFileRename old new)))
- (tide-on-response-success response (:min-version "2.9")
- (tide-do-rename-file old new (plist-get response :body))
- (message "Renamed '%s' to '%s'." name (file-name-nondirectory new)))))))
-
-;;; Format
-
-;;;###autoload
-(defun tide-format-before-save ()
- "Before save hook to format the buffer before each save."
- (interactive)
- (when (bound-and-true-p tide-mode)
- (tide-format)))
-
-;;;###autoload
-(defun tide-format ()
- "Format the current region or buffer."
- (interactive)
- (if (use-region-p)
- (tide-format-region (region-beginning) (region-end))
- (tide-format-region (point-min) (point-max))))
-
-(defun tide-normalize-lineshift (str)
- "Reformat `STR' to only contain line-shift formats expected by Emacs.
-
-When inserting text in an Emacs-buffer Emacs only ever expects \n
-for newlines, no matter what the actual encoding of the file
-is. Inserting anything else causes issues with formatting and
-code-analysis."
-
- ;; convert DOS CR+LF to LF
- (setq str (replace-regexp-in-string "\r\n" "\n" str))
- ;; convert Mac CR to LF
- (setq str (subst-char-in-string ?\r ?\n str))
- str)
-
-(defun tide-insert (str)
- "Insert `STR' into the buffer, but normalize the line-enings."
-
- (insert (tide-normalize-lineshift str)))
-
-(defun tide-apply-edit (edit)
- (goto-char (tide-location-to-point (plist-get edit :start)))
- (delete-region (point) (tide-location-to-point (plist-get edit :end)))
- (let ((start (point-marker)))
- (tide-insert (plist-get edit :newText))
- (cons start (point-marker))))
-
-(defun tide-do-apply-edits (edits)
- (save-excursion
- (-map (lambda (edit) (tide-apply-edit edit))
- (nreverse edits))))
-
-(defun tide-apply-edits (edits)
- (if (and (fboundp 'combine-change-calls)
- (> (length edits) 2))
- (combine-change-calls (point-min) (point-max) (tide-do-apply-edits edits))
- (tide-do-apply-edits edits)))
-
-(defun tide-format-region (start end)
- (let ((response (tide-send-command-sync
- "format"
- `(:file ,(tide-buffer-file-name)
- :line ,(tide-line-number-at-pos start)
- :offset ,(tide-offset start)
- :endLine ,(tide-line-number-at-pos end)
- :endOffset ,(tide-offset end)))))
- (tide-on-response-success response
- (tide-apply-edits (plist-get response :body)))))
-
-(defun tide-format-regions (ranges)
- (let ((positions (->>
- ranges
- (-mapcat (lambda (range) (list (marker-position (car range)) (marker-position (cdr range)))))
- (-sort '<))))
- (tide-format-region (-min positions) (-max positions))))
-
-;;; JSDoc Comment Template
-
-(defun tide-command:docCommentTemplate ()
- (tide-send-command-sync "docCommentTemplate"
- `(:file ,buffer-file-name
- :line ,(tide-line-number-at-pos) :offset ,(tide-current-offset))))
-
-(defun tide-jsdoc-template ()
- "Insert JSDoc comment template at point"
- (interactive)
- (let ((response (tide-command:docCommentTemplate)))
- (tide-on-response-success response
- (progn
- (save-excursion
- (tide-insert (tide-plist-get response :body :newText)))
- (forward-char (tide-plist-get response :body :caretOffset))))))
-
-;;; Mode
-
-(defvar tide-mode-map
- (let ((map (make-sparse-keymap)))
- (define-key map (kbd "M-.") #'tide-jump-to-definition)
- (define-key map (kbd "M-,") #'tide-jump-back)
- map))
-
-(defun tide-configure-buffer ()
- (setq tide-active-buffer-file-name (tide-buffer-file-name))
-
- (tide-command:openfile)
- (tide-command:configure)
-
- ;; tsserver requires non-.ts files to be manually added to the files array in
- ;; tsconfig.json, otherwise the file will be loaded as part of an 'inferred
- ;; project'. This won't be necessary anymore after TypeScript allows defining
- ;; custom file extensions. https://github.com/Microsoft/TypeScript/issues/8328
- (when (and tide-require-manual-setup (tide-buffer-file-name))
- (tide-command:projectInfo
- (lambda (response)
- (tide-on-response-success response
- (when (string-prefix-p "/dev/null/inferredProject"
- (tide-plist-get response :body :configFileName))
- (message (format "'%s' is not part of a project, add it to the files array in tsconfig.json"
- (tide-buffer-file-name)))))))))
-
-(defun tide-configure-buffer-if-server-exists ()
- "Invoke `tide-configure-buffer' only if there is a server running for the
-current buffer."
- (when (tide-current-server)
- (tide-configure-buffer)))
-
-(defun tide-cleanup-buffer ()
- (ignore-errors
- (tide-command:closefile))
- (ignore-errors
- (tide-remove-tmp-file)))
-
-;;;###autoload
-(defun tide-setup ()
- "Setup `tide-mode' in current buffer."
- (interactive)
-
- (when (version< emacs-version tide--minimal-emacs)
- (display-warning 'tide (format "Tide requires Emacs >= %s, you are using %s."
- tide--minimal-emacs emacs-version)
- :error))
-
- ;; Indirect buffers embedded in other major modes such as those in org-mode or
- ;; template languages have to be manually synchronized to tsserver. This might
- ;; cause problems in files with lots of small blocks of TypeScript. In that
- ;; case we should either add an ignore list or don't do anything at all when
- ;; there are more than a certain amount of snippets.
- (unless (stringp buffer-file-name)
- (setq tide-require-manual-setup t))
-
- (set (make-local-variable 'eldoc-documentation-function)
- 'tide-eldoc-function)
- (set (make-local-variable 'imenu-auto-rescan) t)
- (set (make-local-variable 'imenu-create-index-function)
- 'tide-imenu-index)
-
- (tide-mode 1)
-
- (if (tide-current-server)
- ;;
- ;; We want to issue tide-configure-buffer here if the server exists. We
- ;; cannot rely on hack-local-variable-hook in tide-mode because the hook
- ;; may not run at all, or run too late.
- ;;
- ;; It may happen for some use-case scenarios that tide-configure-buffer
- ;; runs more than once with the same data for the same buffer, but that's
- ;; not a big deal.
- ;;
- (tide-configure-buffer)
- (when (eq tide-tsserver-start-method 'immediate)
- (tide-start-server))))
-
-;;;###autoload
-(define-minor-mode tide-mode
- "Minor mode for Typescript Interactive Development Environment.
-
-\\{tide-mode-map}"
- :lighter " tide"
- :keymap tide-mode-map
- (if tide-mode
- (progn
- (add-hook 'after-save-hook 'tide-sync-buffer-contents nil t)
- (add-hook 'after-save-hook 'tide-auto-compile-file nil t)
- (add-hook 'after-change-functions 'tide-handle-change nil t)
- (add-hook 'kill-buffer-hook 'tide-cleanup-buffer nil t)
- (add-hook 'kill-buffer-hook 'tide-schedule-dead-projects-cleanup nil t)
- (add-hook 'hack-local-variables-hook
- 'tide-configure-buffer-if-server-exists nil t)
- (when tide-enable-xref
- (add-hook 'xref-backend-functions #'xref-tide-xref-backend nil t))
- (when (commandp 'typescript-insert-and-indent)
- (eldoc-add-command 'typescript-insert-and-indent)))
- (remove-hook 'after-save-hook 'tide-sync-buffer-contents t)
- (remove-hook 'after-save-hook 'tide-auto-compile-file t)
- (remove-hook 'after-change-functions 'tide-handle-change t)
- (remove-hook 'kill-buffer-hook 'tide-cleanup-buffer t)
- (remove-hook 'kill-buffer-hook 'tide-schedule-dead-projects-cleanup t)
- (remove-hook 'hack-local-variables-hook
- 'tide-configure-buffer-if-server-exists t)
- (remove-hook 'xref-backend-functions #'xref-tide-xref-backend t)
- (tide-cleanup-buffer)))
-
-
-;;; Error highlighting
-
-(defun tide-command:geterr (cb)
- (let* ((result '())
- (resolved nil)
- (err nil))
- (cl-flet ((resolve ()
- (cond (resolved nil)
- (err (setq resolved t)
- (funcall cb err))
- ((and (plist-member result :syntaxDiag)
- (plist-member result :semanticDiag)
- (plist-member result :suggestionDiag))
- (setq resolved t)
- (funcall cb `(:body (,result) :success t))))))
- (tide-send-command
- "syntacticDiagnosticsSync"
- `(:file ,(tide-buffer-file-name))
- (lambda (response)
- (if (tide-response-success-p response)
- (setq result (plist-put result :syntaxDiag (plist-get response :body)))
- (setq err response))
- (resolve)))
- (tide-send-command
- "semanticDiagnosticsSync"
- `(:file ,(tide-buffer-file-name))
- (lambda (response)
- (if (tide-response-success-p response)
- (setq result (plist-put result :semanticDiag (plist-get response :body)))
- (setq err response))
- (resolve)))
-
- (if tide-disable-suggestions
- (setq result (plist-put result :suggestionDiag '()))
- (tide-send-command
- "suggestionDiagnosticsSync"
- `(:file ,(tide-buffer-file-name))
- (lambda (response)
- (cond
- ((tide-response-success-p response)
- (setq result (plist-put result :suggestionDiag (plist-get response :body))))
- ((tide-command-unknown-p response)
- (setq result (plist-put result :suggestionDiag '())))
- (t (setq err response)))
- (resolve)))))))
-
-(defun tide-parse-error (response checker)
- (-map
- (lambda (diagnostic)
- (let* ((start (plist-get diagnostic :start))
- (line (plist-get start :line))
- (column (plist-get start :offset))
- (level (if (string= (plist-get diagnostic :category) "suggestion") 'info 'error))
- (text (plist-get diagnostic :text)))
- (when (plist-get diagnostic :relatedInformation)
- (setq text (concat text (propertize " ⮐" 'face 'font-lock-warning-face))))
- (put-text-property 0 1 'diagnostic diagnostic text)
- (flycheck-error-new-at line column level text
- :checker checker
- :id (plist-get diagnostic :code))))
- (let ((diagnostic (car (tide-plist-get response :body))))
- (-concat (plist-get diagnostic :syntaxDiag)
- (plist-get diagnostic :semanticDiag)
- (plist-get diagnostic :suggestionDiag)))))
-
-(defun tide-flycheck-send-response (callback checker response)
- (condition-case err
- (funcall callback 'finished (tide-parse-error response checker))
- (error (funcall callback 'errored (error-message-string err)))))
-
-(defun tide-flycheck-start (checker callback)
- (tide-command:geterr
- (lambda (response)
- (when (tide-command-unknown-p response)
- (tide-tsserver-version-not-supported))
- (if (tide-response-success-p response)
- (tide-flycheck-send-response callback checker response)
- (funcall callback 'errored (plist-get response :message))))))
-
-(defun tide-make-clickable-filespan (filespan)
- (propertize
- (concat
- (file-name-nondirectory (plist-get filespan :file))
- ":"
- (number-to-string (tide-plist-get filespan :start :line)))
- 'face 'link
- 'help-echo "mouse-2: go to this location"
- 'keymap (let ((map (make-sparse-keymap)))
- (define-key map [mouse-2] 'tide-goto-error)
- (define-key map [mouse-1] 'tide-goto-error)
- (define-key map (kbd "RET") 'tide-goto-error)
- (define-key map [follow-link] 'mouse-face)
- map)
- 'tide-error filespan))
-
-(defun tide-format-related-information (related)
- (concat
- (tide-make-clickable-filespan (plist-get related :span))
- " "
- (plist-get related :message)
- " [" (number-to-string (plist-get related :code)) "]"))
-
-(defun tide-explain-error (err)
- (let* ((diagnostic (get-text-property 0 'diagnostic (flycheck-error-message err)))
- (related (plist-get diagnostic :relatedInformation)))
- (concat
- (propertize "Code: " 'face 'bold) (number-to-string (plist-get diagnostic :code)) " "
- (propertize "Category: " 'face 'bold) (plist-get diagnostic :category)
- "\n\n"
- (plist-get diagnostic :text)
- "\n\n"
- (when related
- (concat
- (propertize "Related Information: \n\n" 'face 'bold)
- (mapconcat 'tide-format-related-information related "\n\n"))))))
-
-(defun tide-error-at-point ()
- "Show the details of the error at point."
- (interactive)
- (-if-let (errors (flycheck-overlay-errors-at (point)))
- (tide-display-help-buffer "error"
- (mapconcat #'tide-explain-error errors "\n\n--------\n\n"))
- (message "No errors available.")))
-
-(defun tide-flycheck-verify (_checker)
- (list
- (flycheck-verification-result-new
- :label "Typescript server"
- :message (if (tide-current-server) "running" "not running")
- :face (if (tide-current-server) 'success '(bold error)))
- (flycheck-verification-result-new
- :label "Tide mode"
- :message (if (bound-and-true-p tide-mode) "enabled" "disabled")
- :face (if (bound-and-true-p tide-mode) 'success '(bold warning)))))
-
-(defun tide-flycheck-predicate ()
- (and (bound-and-true-p tide-mode)
- (tide-current-server)
- (not (file-equal-p (tide-project-root) tide-tsserver-directory))))
-
-(defun tide-file-extension-p (ext)
- (and buffer-file-name
- (string-equal ext (file-name-extension buffer-file-name))))
-
-(flycheck-define-generic-checker 'typescript-tide
- "A TypeScript syntax checker using tsserver."
- :start #'tide-flycheck-start
- :verify #'tide-flycheck-verify
- :modes '(typescript-mode)
- :predicate #'tide-flycheck-predicate)
-
-(add-to-list 'flycheck-checkers 'typescript-tide)
-
-(flycheck-define-generic-checker 'javascript-tide
- "A Javascript syntax checker using tsserver."
- :start #'tide-flycheck-start
- :verify #'tide-flycheck-verify
- :modes '(js-mode js2-mode js3-mode)
- :predicate #'tide-flycheck-predicate)
-
-(add-to-list 'flycheck-checkers 'javascript-tide t)
-
-(flycheck-define-generic-checker 'jsx-tide
- "A JSX syntax checker using tsserver."
- :start #'tide-flycheck-start
- :verify #'tide-flycheck-verify
- :modes '(web-mode js2-jsx-mode rjsx-mode)
- :predicate (lambda ()
- (and
- (tide-file-extension-p "jsx")
- (tide-flycheck-predicate))))
-
-(add-to-list 'flycheck-checkers 'jsx-tide t)
-
-(flycheck-define-generic-checker 'tsx-tide
- "A TSX syntax checker using tsserver."
- :start #'tide-flycheck-start
- :verify #'tide-flycheck-verify
- :modes '(web-mode)
- :predicate (lambda ()
- (and
- (tide-file-extension-p "tsx")
- (tide-flycheck-predicate))))
-
-(add-to-list 'flycheck-checkers 'tsx-tide)
-
-;;; Project errors
-
-(defun tide-command:geterrForProject (file)
- (tide-send-command
- "geterrForProject"
- `(:file ,file :delay 0)))
-
-(defun tide-project-errors-buffer-name ()
- (format "*%s-errors*" (tide-project-name)))
-
-(defun tide-display-errors (file-names origin-buffer-file-name)
- (let ((origin-buffer (current-buffer)))
- (with-current-buffer (get-buffer-create (tide-project-errors-buffer-name))
- (tide-project-errors-mode)
- (setq-local tide-origin-buffer-file-name origin-buffer-file-name)
- (let ((inhibit-read-only t))
- (erase-buffer))
- (when (not (equal origin-buffer (current-buffer)))
- (display-buffer (current-buffer) t))
- (let* ((project-files (-reject (lambda (file-name)
- (or (string-match-p "node_modules" file-name)
- (string-match-p "tsconfig.json$" file-name)))
- file-names))
- (syntax-remaining-files (cl-copy-list project-files))
- (semantic-remaining-files (cl-copy-list project-files))
- (suggestion-remaining-files (if tide-disable-suggestions
- '()
- (cl-copy-list project-files)))
- (syntax-errors 0)
- (semantic-errors 0)
- (suggestion-errors 0)
- (last-file-name nil))
- (tide-set-event-listener
- (lambda (response)
- (save-excursion
- (goto-char (point-max))
- (let ((inhibit-read-only t)
- (file-name (tide-plist-get response :body :file))
- (diagnostics (tide-plist-get response :body :diagnostics))
- (event-type (plist-get response :event)))
- (when (and
- (not (and (string-equal event-type "suggestionDiag") tide-disable-suggestions))
- (member file-name project-files))
- (pcase event-type
- ("syntaxDiag"
- (setq syntax-remaining-files (remove file-name syntax-remaining-files))
- (cl-incf syntax-errors (length diagnostics)))
- ("semanticDiag"
- (setq semantic-remaining-files (remove file-name semantic-remaining-files))
- (cl-incf semantic-errors (length diagnostics)))
- ("suggestionDiag"
- (setq suggestion-remaining-files (remove file-name suggestion-remaining-files))
- (cl-incf suggestion-errors (length diagnostics))))
-
- (when diagnostics
- (-each diagnostics
- (lambda (diagnostic)
- (let ((line-number (tide-plist-get diagnostic :start :line)))
- (unless (equal last-file-name file-name)
- (setq last-file-name file-name)
- (insert (propertize (file-relative-name file-name (tide-project-root)) 'face 'tide-file)
- "\n"))
-
- (insert (propertize (format "%5d" line-number)
- 'face 'tide-line-number
- 'tide-error (plist-put diagnostic :file file-name))
- ": " (plist-get diagnostic :text) "\n")))))
- (when (and (null syntax-remaining-files) (null semantic-remaining-files) (null suggestion-remaining-files))
- (insert (format "\n%d syntax error(s), %d semantic error(s), %d suggestion error(s)\n"
- syntax-errors semantic-errors suggestion-errors))
- (goto-char (point-min))
- (tide-clear-event-listener))))))))))
- (tide-command:geterrForProject origin-buffer-file-name))
-
-(defun tide-next-error-function (n &optional reset)
- "Override for `next-error-function' for use in tide-project-errors-mode buffers."
- (interactive "p")
-
- (-when-let (buffer (get-buffer (tide-project-errors-buffer-name)))
- (with-current-buffer buffer
- (when reset
- (goto-char (point-min)))
- (if (> n 0)
- (tide-find-next-error (point) n)
- (tide-find-previous-error (point) (- n)))
- (tide-goto-error))))
-
-(defun tide-find-next-error (pos arg)
- "Move to next error."
- (interactive "d\np")
- (setq arg (* 2 arg))
- (unless (get-text-property pos 'tide-error)
- (setq arg (1- arg)))
- (dotimes (_i arg)
- (setq pos (next-single-property-change pos 'tide-error))
- (unless pos
- (error "Moved past last error")))
- (goto-char pos))
-
-(defun tide-find-previous-error (pos arg)
- "Move back to previous error."
- (interactive "d\np")
- (dotimes (_i (* 2 arg))
- (setq pos (previous-single-property-change pos 'tide-error))
- (unless pos
- (error "Moved back before first error")))
- (goto-char pos))
-
-(defun tide-goto-error ()
- "Jump to error location in the file."
- (interactive)
- (-when-let (error (get-text-property (point) 'tide-error))
- (tide-jump-to-filespan error nil t)))
-
-(defvar tide-project-errors-mode-map
- (let ((map (make-sparse-keymap)))
- (define-key map (kbd "n") #'tide-find-next-error)
- (define-key map (kbd "p") #'tide-find-previous-error)
- (define-key map (kbd "g") #'tide-project-errors)
- (define-key map (kbd "RET") #'tide-goto-error)
- map))
-
-(define-derived-mode tide-project-errors-mode special-mode "tide-project-errors"
- "Major mode for tide project-errors list.
-
-\\{tide-project-errors-mode-map}"
- (setq next-error-function #'tide-next-error-function))
-
-;;;###autoload
-(defun tide-project-errors ()
- (interactive)
- (let ((file (if (string= major-mode "tide-project-errors-mode")
- tide-origin-buffer-file-name
- (tide-buffer-file-name))))
- (tide-command:projectInfo
- (lambda (response)
- (tide-on-response-success response
- (tide-display-errors (tide-plist-get response :body :fileNames) file)))
- t
- file)))
-
-;;; Identifier highlighting
-(defun tide-command:documentHighlights (cb)
- (tide-send-command
- "documentHighlights"
- `(:file ,(tide-buffer-file-name) :line ,(tide-line-number-at-pos) :offset ,(tide-current-offset)
- :filesToSearch [,(tide-buffer-file-name)])
- cb))
-
-(defface tide-hl-identifier-face
- '((t (:inherit highlight)))
- "Face used for highlighting identifiers in `tide-hl-identifier'."
- :group 'tide)
-
-(defcustom tide-hl-identifier-idle-time 0.50
- "How long to wait after user input before highlighting the current identifier."
- :type 'float
- :group 'tide)
-
-(tide-def-permanent-buffer-local tide--hl-last-token 0)
-
-(defun tide--hl-new-token ()
- "Invalidate all existing tokens to get document highlights and
-create a new token"
- (cl-incf tide--hl-last-token))
-
-(defvar tide--current-hl-identifier-idle-time
- 0
- "The current delay for hl-identifier-mode.")
-
-(defvar tide--hl-identifier-timer
- nil
- "The global timer used for highlighting identifiers.")
-
-;;;###autoload
-(defun tide-unhighlight-identifiers ()
- "Remove highlights from previously highlighted identifier."
- (tide--hl-new-token)
- (remove-overlays nil nil 'tide-overlay 'sameid))
-
-;;;###autoload
-(defun tide-hl-identifier ()
- "Highlight all instances of the identifier under point. Removes
-highlights from previously highlighted identifier."
- (interactive)
- (tide-unhighlight-identifiers)
- (tide--hl-identifier))
-
-(defun tide--hl-identifier ()
- "Highlight all instances of the identifier under point."
- (let ((token (tide--hl-new-token)))
- (tide-command:documentHighlights
- (lambda (response)
- (when (and
- (equal token tide--hl-last-token)
- (tide-response-success-p response))
- (tide--hl-highlight response))))))
-
-(defun tide--hl-highlight (response)
- "Highlight all instances of the identifier under point."
- (-when-let* ((item (-first (lambda (item)
- (equal (tide-buffer-file-name) (plist-get item :file)))
- (plist-get response :body)))
- (references (plist-get item :highlightSpans)))
- ;; `overlay-recenter' provide a modest speed improvement when creating lots
- ;; of overlays from a list. See the documentation on this function for
- ;; details.
- (overlay-recenter (point-max))
- ;; The point computations in this loop *could* be replaced with
- ;; `tide-location-to-point' but `tide-location-to-point' is extremely slow
- ;; when it comes to processing large lists of locations because it returns
- ;; to `point-min' with each call.
- (save-excursion
- (save-restriction
- (widen)
- (goto-char (point-min))
- (let ((current-line 1))
- (dolist (reference references)
- (when (member (plist-get reference :kind) '("reference" "writtenReference"))
- (let* ((start (plist-get reference :start))
- (end (plist-get reference :end))
- (start-line (plist-get start :line))
- (end-line (plist-get end :line))
- (ostart (progn (forward-line (- start-line current-line))
- (forward-char (1- (plist-get start :offset)))
- (point)))
- (oend (if (= start-line end-line)
- (progn
- (forward-char (- (plist-get end :offset)
- (plist-get start :offset)))
- (point))
- (forward-line (- end-line start-line))
- (forward-char (1- (plist-get end :offset)))
- (point)))
- (overlay (make-overlay ostart oend)))
- (setq current-line end-line)
- (overlay-put overlay 'tide-overlay 'sameid)
- (overlay-put overlay 'face 'tide-hl-identifier-face)))))))))
-
-;;;###autoload
-(define-minor-mode tide-hl-identifier-mode
- "Highlight instances of the identifier at point after a short
-timeout."
- :group 'tide
- (if tide-hl-identifier-mode
- (progn
- (tide--hl-set-timer)
- ;; Unhighlight if point moves off identifier
- (add-hook 'post-command-hook #'tide--hl-identifiers-post-command-hook nil t)
- ;; Unhighlight any time the buffer changes
- (add-hook 'before-change-functions #'tide--hl-identifiers-before-change-function nil t))
- (remove-hook 'post-command-hook #'tide--hl-identifiers-post-command-hook t)
- (remove-hook 'before-change-functions #'tide--hl-identifiers-before-change-function t)
- (tide-unhighlight-identifiers)))
-
-(defun tide--hl-identifiers-function ()
- "Function run after an idle timeout, highlighting the
-identifier at point, if necessary."
- (when tide-hl-identifier-mode
- (unless (tide--on-overlay-p 'sameid)
- (tide-hl-identifier))
- (unless (eq tide--current-hl-identifier-idle-time tide-hl-identifier-idle-time)
- (tide--hl-set-timer))))
-
-(defun tide--hl-set-timer ()
- (when tide--hl-identifier-timer
- (cancel-timer tide--hl-identifier-timer))
- (setq tide--current-hl-identifier-idle-time tide-hl-identifier-idle-time)
- (setq tide--hl-identifier-timer (run-with-idle-timer
- tide-hl-identifier-idle-time
- t
- #'tide--hl-identifiers-function)))
-
-(defun tide--on-overlay-p (id)
- "Return whether point is on a tide overlay of type ID."
- (cl-find-if (lambda (el) (eq (overlay-get el 'tide-overlay) id)) (overlays-at (point))))
-
-(defun tide--hl-identifiers-post-command-hook ()
- (when (and tide-hl-identifier-mode
- (not (tide--on-overlay-p 'sameid)))
- (tide-unhighlight-identifiers)))
-
-(defun tide--hl-identifiers-before-change-function (_beg _end)
- (tide-unhighlight-identifiers))
-
-
-;;; Compile On Save
-
-(defun tide-command:compileOnSaveEmitFile ()
- (tide-send-command "compileOnSaveEmitFile" `(:file ,(tide-buffer-file-name))))
-
-(defun tide-compile-file ()
- "Compiles the current file"
- (interactive)
- (tide-command:compileOnSaveEmitFile))
-
-(defun tide-auto-compile-file ()
- "Compiles the current file if compileOnSave is set"
- (interactive)
- (-when-let (config (tide-project-config))
- ;; Pre-v3.6.2 tsc converts compileOnSave to compilerOnSave
- (when (or (eq (plist-get config :compilerOnSave) t)
- (eq (plist-get config :compileOnSave) t))
- (tide-command:compileOnSaveEmitFile))))
-
-(defun tide-project-config ()
- (let ((config (gethash (tide-project-name) tide-project-configs :not-loaded)))
- (if (eq config :not-loaded)
- (let* ((default-directory (tide-project-root))
- (tscjs (tide-locate-tscompiler-executable)))
- (if tscjs
- (let ((config (tide-safe-json-read-string
- (tide-command-to-string tide-node-executable (list tscjs "--showConfig")))))
- (puthash (tide-project-name) config tide-project-configs))
- (puthash (tide-project-name) '() tide-project-configs)))
- config)))
-
-;;; Utility commands
-
-(defun tide-kill-server ()
- "Kill the server in the current buffer."
- (interactive)
- (-when-let (server (tide-current-server))
- (delete-process server)))
-
-(defun tide-restart-server ()
- "Restarts tsserver."
- (interactive)
- (tide-kill-server)
- (tide-start-server))
-
-(defvar-local tide--server-list-mode-last-column nil)
-
-(defun tide--list-servers-verify-setup (button)
- "Invoke `tide-verify-setup' on a tsserver displayed in the list of server."
- (tide-first-buffer (button-get button 'project-name) #'tide-verify-setup))
-
-;; This is modeled after list-process--refresh but we do not call delete-process
-;; on exited or signaled processe. That seems inappropriate for a function
-;; designed to *report* information.
-(defun tide--list-servers-refresh ()
- "Recompute the list of processes for the buffer displayed by
-`tide-list-servers'."
- (setq tabulated-list-entries nil)
- (let ((tsservers (hash-table-values tide-servers)))
- (dolist (p tsservers)
- (let* ((project-name (process-get p 'project-name))
- (pid (process-id p))
- (cpu
- (if (tide--emacs-at-least "25")
- (alist-get 'pcpu (process-attributes pid))
- (cdr (assq 'pcpu (process-attributes pid))))))
- (push (list p
- (vector
- `(,project-name
- face link
- help-echo
- ,(if (tide--emacs-at-least "25")
- (format-message "Verify setup of `%s'" project-name)
- (concat "Verify setup of `" project-name "'"))
- follow-link t
- project-name ,project-name
- action tide--list-servers-verify-setup)
- ;; Sometimes the CPU usage value is NaN (which Emacs represents
- ;; as 0.0e+NaN), for whatever reason. We cannot pass this value
- ;; to round so we put "--" for the column value.
- ;; Other times, it is nil, so do the same then.
- (if (or (not cpu) (isnan cpu))
- "--"
- (number-to-string (round cpu)))
- (cl-case tide--server-list-mode-last-column
- ((project-root)
- (or (process-get p 'project-root) ""))
- ((command)
- (mapconcat 'identity (process-command p) " "))
- (t (error "unknown column %s"
- tide--server-list-mode-last-column)))))
- tabulated-list-entries)))))
-
-(defun tide--server-list-kill-server ()
- "Kill a tsserver instance."
- (interactive)
- (let ((process (tabulated-list-get-id)))
- (delete-process process)
- (revert-buffer)))
-
-(defvar tide--server-list-last-column-choice-list
- '(project-root command)
- "The possible choices for the last column, as a circular list.")
-
-(defun tide--server-list-cycle-last-column ()
- "Cycle through the possible values for the last column."
- (interactive)
- (setq tide--server-list-mode-last-column
- (or (cadr (or (memq tide--server-list-mode-last-column
- tide--server-list-last-column-choice-list)
- (error "%s is not a possible choice of last column."
- tide--server-list-mode-last-column)))
- (car tide--server-list-last-column-choice-list)))
- (tide--setup-list-mode)
- (revert-buffer))
-
-(defvar tide-server-list-mode-map
- (let ((map (make-sparse-keymap)))
- (define-key map [?d] 'tide--server-list-kill-server)
- (define-key map [?/] 'tide--server-list-cycle-last-column)
- map))
-
-(defun tide--setup-list-mode ()
- (setq tabulated-list-format
- (vector
- '("Project Name" 20 t)
- `("CPU" 5 ,(lambda (a b)
- (let* ((cpu-a (elt (cadr a) 1))
- (cpu-b (elt (cadr b) 1)))
- ;; The CPU usage value in the column can be "--" if Emacs
- ;; produced a NaN value. We consider "--" to be less than numbers.
- (cond
- ((string= cpu-a "--")
- (not (string= cpu-b "--")))
- ((string= cpu-b "--") nil)
- (t
- (< (string-to-number cpu-a)
- (string-to-number cpu-b)))))))
- (list
- (cl-case tide--server-list-mode-last-column
- ((project-root) "Project Root")
- ((command) "Project Command")
- (t (error "unknown column %s" tide--server-list-mode-last-column)))
- 0 t)))
- (setq tabulated-list-sort-key (cons "Project Name" nil))
- (tabulated-list-init-header))
-
-(define-derived-mode tide-server-list-mode tabulated-list-mode "tide-server-list-mode"
- "Major mode for listing tsserver processes."
- (setq-local tide--server-list-mode-last-column 'project-root)
- (add-hook 'tabulated-list-revert-hook 'tide--list-servers-refresh nil t)
- (tide--setup-list-mode))
-
-;; This is modeled after list-processes.
-(defun tide-list-servers (&optional buffer)
- "Lists the tsserver processes known to tide."
- (interactive)
- (unless (bufferp buffer)
- (setq buffer (get-buffer-create "*Tide Server List*")))
- (with-current-buffer buffer
- (tide-server-list-mode)
- (tide--list-servers-refresh)
- (tabulated-list-print))
- (display-buffer buffer)
- nil)
-
-(defun tide-command:status ()
- (tide-send-command-sync "status" '()))
-
-(defun tide-show-project-info (version config-file-name)
- (with-current-buffer (get-buffer-create "*tide-project-info*")
- (let ((inhibit-read-only t))
- (erase-buffer)
- (insert "tsserver version: "
- (propertize version 'face '(success bold))
- "\n\n"
- "config file path: "
- (propertize config-file-name 'face 'success)))
- (special-mode)
- (display-buffer (current-buffer) t)))
-
-(defun tide-verify-setup ()
- "Show the version of tsserver."
- (interactive)
- (let ((response (tide-command:status)))
- (tide-on-response-success response (:min-version "2.7")
- (let ((version (tide-plist-get response :body :version)))
- (tide-command:projectInfo
- (lambda (response)
- (tide-on-response-success response
- (let ((config-file-name (tide-plist-get response :body :configFileName)))
- (tide-show-project-info version config-file-name)))))))))
-
-
-;;; xref integration
-
-(defun xref-tide-xref-backend ()
- "Xref-tide backend for xref."
- 'xref-tide)
-
-(cl-defmethod xref-backend-identifier-at-point ((_backend (eql xref-tide)))
- (let ((symbol (tide-get-symbol-at-point)))
- (when (and symbol
- (not (string-equal symbol "")))
- (put-text-property 0 1 'location `(:file ,(tide-buffer-file-name) :line ,(tide-line-number-at-pos) :offset ,(tide-current-offset)) symbol))
- symbol))
-
-(defvar tide-xref--last-completion-table '())
-
-(cl-defmethod xref-backend-identifier-completion-table ((_backend (eql xref-tide)))
- (completion-table-dynamic
- (lambda (prefix)
- (let ((response (tide-command:navto prefix t)))
- (tide-on-response-success response
- (-when-let (navto-items (plist-get response :body))
- (setq tide-xref--last-completion-table navto-items)
- (-map (lambda (navto-item) (plist-get navto-item :name)) navto-items)))))
- t))
-
-(cl-defmethod xref-backend-identifier-completion-ignore-case ((_backend (eql xref-tide)))
- tide-completion-ignore-case)
-
-(cl-defmethod xref-backend-references ((_backend (eql xref-tide)) symbol)
- (tide-xref--find-references symbol))
-
-(cl-defmethod xref-backend-definitions ((_backend (eql xref-tide)) symbol)
- (tide-xref--find-definitions symbol))
-
-(cl-defmethod xref-backend-apropos ((_backend (eql xref-tide)) pattern)
- (tide-xref--find-apropos pattern))
-
-(defun tide-xref--make-reference (reference)
- "Make xref object from RERERENCE."
- (let ((full-file-name (plist-get reference :file))
- (line-number (tide-plist-get reference :start :line))
- (line-text (plist-get reference :lineText))
- (start (1- (tide-plist-get reference :start :offset)))
- (end (1- (tide-plist-get reference :end :offset))))
- (put-text-property start end 'face 'tide-match line-text)
- (xref-make line-text
- (xref-make-file-location full-file-name
- line-number
- start))))
-
-(defun tide-xref--make-definition (symbol definition)
- "Make xref object from DEFINITION."
- (let ((full-file-name (plist-get definition :file))
- (line-number (tide-plist-get definition :start :line))
- (start (1- (tide-plist-get definition :start :offset))))
- (xref-make symbol
- (xref-make-file-location full-file-name
- line-number
- start))))
-
-(defun tide-xref--make-navto (pattern navto)
- "Make xref object from NAVTO."
- (let ((full-file-name (plist-get navto :file))
- (line-text (tide-file-span-first-line-text navto pattern))
- (line-number (tide-plist-get navto :start :line))
- (start (1- (tide-plist-get navto :start :offset))))
- (xref-make line-text
- (xref-make-file-location full-file-name
- line-number
- start))))
-
-(defun tide-xref--symbol-location (symbol)
- (-if-let (location (get-text-property 0 'location symbol))
- location
- (-when-let (navto-item (-find (lambda (navto-item) (string-equal symbol (plist-get navto-item :name)))
- tide-xref--last-completion-table))
- (save-restriction
- (save-excursion
- (widen)
- (tide-move-to-location (plist-get navto-item :start))
- (search-forward symbol (tide-location-to-point (plist-get navto-item :end)))
- `(:file ,(plist-get navto-item :file)
- :line ,(tide-line-number-at-pos)
- :offset ,(tide-current-offset)))))))
-
-
-(defun tide-xref--find-definitions (symbol)
- "Return xref definition objects."
- (let ((response (tide-command:definition nil (tide-xref--symbol-location symbol))))
- (tide-on-response-success response
- (let ((definitions (tide-plist-get response :body)))
- (-map (apply-partially #'tide-xref--make-definition symbol) definitions)))))
-
-(defun tide-xref--find-references (symbol)
- "Return xref reference objects."
- (let ((response (tide-command:references (tide-xref--symbol-location symbol))))
- (tide-on-response-success response
- (let ((references (tide-plist-get response :body :refs)))
- (-map #'tide-xref--make-reference references)))))
-
-(defun tide-xref--find-apropos (pattern)
- "Return xref navto objects."
- (let ((response (tide-command:navto pattern)))
- (tide-on-response-success response
- (-when-let (navto-items (plist-get response :body))
- (-map (apply-partially #'tide-xref--make-navto pattern) navto-items)))))
-
-(provide 'tide)
-
-;;; tide.el ends here