aboutsummaryrefslogtreecommitdiffstats
path: root/elpa/lsp-mode-20220505.630/lsp-semantic-tokens.el
diff options
context:
space:
mode:
Diffstat (limited to 'elpa/lsp-mode-20220505.630/lsp-semantic-tokens.el')
-rw-r--r--elpa/lsp-mode-20220505.630/lsp-semantic-tokens.el876
1 files changed, 876 insertions, 0 deletions
diff --git a/elpa/lsp-mode-20220505.630/lsp-semantic-tokens.el b/elpa/lsp-mode-20220505.630/lsp-semantic-tokens.el
new file mode 100644
index 0000000..35fb8ba
--- /dev/null
+++ b/elpa/lsp-mode-20220505.630/lsp-semantic-tokens.el
@@ -0,0 +1,876 @@
+;;; lsp-semantic-tokens.el --- Semantic tokens -*- lexical-binding: t; -*-
+;;
+;; Copyright (C) 2020 emacs-lsp maintainers
+;;
+;; 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 <https://www.gnu.org/licenses/>.
+;;
+;;; Commentary:
+;;
+;; Semantic tokens
+;; https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_semanticTokens
+;;
+;;; Code:
+
+(require 'lsp-mode)
+(require 'dash)
+
+(defgroup lsp-semantic-tokens nil
+ "LSP support for semantic-tokens."
+ :prefix "lsp-semantic-tokens-"
+ :group 'lsp-mode
+ :tag "LSP Semantic tokens")
+
+(define-obsolete-variable-alias 'lsp-semantic-highlighting-warn-on-missing-face 'lsp-semantic-tokens-warn-on-missing-face "lsp-mode 8.0.0")
+
+(defcustom lsp-semantic-tokens-warn-on-missing-face nil
+ "Warning on missing face for token type/modifier.
+When non-nil, this option will emit a warning any time a token
+or modifier type returned by a language server has no face associated with it."
+ :group 'lsp-semantic-tokens
+ :type 'boolean)
+
+(defcustom lsp-semantic-tokens-apply-modifiers t
+ "Whether semantic tokens should take token modifiers into account."
+ :group 'lsp-semantic-tokens
+ :type 'boolean)
+
+(defcustom lsp-semantic-tokens-allow-ranged-requests t
+ "Whether to use ranged semantic token requests when available.
+
+Note that even when this is set to t, delta requests will
+be preferred whenever possible, unless
+`lsp-semantic-tokens-allow-delta-requests' is false."
+ :group 'lsp-semantic-tokens
+ :type 'boolean)
+
+(defcustom lsp-semantic-tokens-allow-delta-requests t
+ "Whether to use semantic token delta requests when available.
+
+When supported by the language server, delta requests are always
+preferred over both full and ranged token requests."
+ :group 'lsp-semantic-tokens
+ :type 'boolean)
+
+(defcustom lsp-semantic-tokens-honor-refresh-requests nil
+ "Whether to honor semanticTokens/refresh requests.
+
+When set to nil, refresh requests will be silently discarded.
+When set to t, semantic tokens will be re-requested for all buffers
+associated with the requesting language server."
+ :group 'lsp-semantic-tokens
+ :type 'boolean)
+
+(defface lsp-face-semhl-constant
+ '((t :inherit font-lock-constant-face))
+ "Face used for semantic highlighting scopes matching constant scopes."
+ :group 'lsp-semantic-tokens)
+
+(defface lsp-face-semhl-variable
+ '((t :inherit font-lock-variable-name-face))
+ "Face used for semantic highlighting scopes matching variable.*.
+Unless overridden by a more specific face association."
+ :group 'lsp-semantic-tokens)
+
+(defface lsp-face-semhl-function
+ '((t :inherit font-lock-function-name-face))
+ "Face used for semantic highlighting scopes matching entity.name.function.*.
+Unless overridden by a more specific face association."
+ :group 'lsp-semantic-tokens)
+
+(defface lsp-face-semhl-method
+ '((t :inherit lsp-face-semhl-function))
+ "Face used for semantic highlighting scopes matching entity.name.method.*.
+Unless overridden by a more specific face association."
+ :group 'lsp-semantic-tokens)
+
+(defface lsp-face-semhl-namespace
+ '((t :inherit font-lock-type-face :weight bold))
+ "Face used for semantic highlighting scopes matching entity.name.namespace.*.
+Unless overridden by a more specific face association."
+ :group 'lsp-semantic-tokens)
+
+(defface lsp-face-semhl-comment
+ '((t (:inherit font-lock-comment-face)))
+ "Face used for comments."
+ :group 'lsp-semantic-tokens)
+
+(defface lsp-face-semhl-keyword
+ '((t (:inherit font-lock-keyword-face)))
+ "Face used for keywords."
+ :group 'lsp-semantic-tokens)
+
+(defface lsp-face-semhl-string
+ '((t (:inherit font-lock-string-face)))
+ "Face used for keywords."
+ :group 'lsp-semantic-tokens)
+
+(defface lsp-face-semhl-number
+ '((t (:inherit font-lock-constant-face)))
+ "Face used for numbers."
+ :group 'lsp-semantic-tokens)
+
+(defface lsp-face-semhl-regexp
+ '((t (:inherit font-lock-string-face :slant italic)))
+ "Face used for regexps."
+ :group 'lsp-semantic-tokens)
+
+(defface lsp-face-semhl-operator
+ '((t (:inherit font-lock-function-name-face)))
+ "Face used for operators."
+ :group 'lsp-semantic-tokens)
+
+(defface lsp-face-semhl-namespace
+ '((t (:inherit font-lock-keyword-face)))
+ "Face used for namespaces."
+ :group 'lsp-semantic-tokens)
+
+(defface lsp-face-semhl-type
+ '((t (:inherit font-lock-type-face)))
+ "Face used for types."
+ :group 'lsp-semantic-tokens)
+
+(defface lsp-face-semhl-struct
+ '((t (:inherit font-lock-type-face)))
+ "Face used for structs."
+ :group 'lsp-semantic-tokens)
+
+(defface lsp-face-semhl-class
+ '((t (:inherit font-lock-type-face)))
+ "Face used for classes."
+ :group 'lsp-semantic-tokens)
+
+(defface lsp-face-semhl-interface
+ '((t (:inherit font-lock-type-face)))
+ "Face used for interfaces."
+ :group 'lsp-semantic-tokens)
+
+(defface lsp-face-semhl-enum
+ '((t (:inherit font-lock-type-face)))
+ "Face used for enums."
+ :group 'lsp-semantic-tokens)
+
+(defface lsp-face-semhl-type-parameter
+ '((t (:inherit font-lock-type-face)))
+ "Face used for type parameters."
+ :group 'lsp-semantic-tokens)
+
+;; function face already defined, move here when support
+;; for theia highlighting gets removed
+(defface lsp-face-semhl-member
+ '((t (:inherit font-lock-variable-name-face)))
+ "Face used for members."
+ :group 'lsp-semantic-tokens)
+
+(defface lsp-face-semhl-property
+ '((t (:inherit font-lock-variable-name-face)))
+ "Face used for properties."
+ :group 'lsp-semantic-tokens)
+
+(defface lsp-face-semhl-event
+ '((t (:inherit font-lock-variable-name-face)))
+ "Face used for event properties."
+ :group 'lsp-semantic-tokens)
+
+(defface lsp-face-semhl-macro
+ '((t (:inherit font-lock-preprocessor-face)))
+ "Face used for macros."
+ :group 'lsp-semantic-tokens)
+
+(defface lsp-face-semhl-variable
+ '((t (:inherit font-lock-variable-name-face)))
+ "Face used for variables."
+ :group 'lsp-semantic-tokens)
+
+(defface lsp-face-semhl-parameter
+ '((t (:inherit font-lock-variable-name-face)))
+ "Face used for parameters."
+ :group 'lsp-semantic-tokens)
+
+(defface lsp-face-semhl-label
+ '((t (:inherit font-lock-comment-face)))
+ "Face used for labels."
+ :group 'lsp-semantic-tokens)
+
+(defface lsp-face-semhl-deprecated
+ '((t :strike-through t))
+ "Face used for semantic highlighting scopes matching constant scopes."
+ :group 'lsp-semantic-tokens)
+
+(defface lsp-face-semhl-definition
+ '((t :inherit font-lock-function-name-face :weight bold))
+ "Face used for definition modifier."
+ :group 'lsp-semantic-tokens)
+
+(defface lsp-face-semhl-implementation
+ '((t :inherit font-lock-function-name-face :weight bold))
+ "Face used for implementation modifier."
+ :group 'lsp-semantic-tokens)
+
+(defface lsp-face-semhl-default-library
+ '((t :inherit font-lock-builtin-face))
+ "Face used for defaultLibrary modifier."
+ :group 'lsp-semantic-tokens)
+
+(defface lsp-face-semhl-static
+ '((t :inherit font-lock-keyword-face))
+ "Face used for static modifier."
+ :group 'lsp-semantic-tokens)
+
+(defvar lsp-semantic-token-faces
+ '(("comment" . lsp-face-semhl-comment)
+ ("keyword" . lsp-face-semhl-keyword)
+ ("string" . lsp-face-semhl-string)
+ ("number" . lsp-face-semhl-number)
+ ("regexp" . lsp-face-semhl-regexp)
+ ("operator" . lsp-face-semhl-operator)
+ ("namespace" . lsp-face-semhl-namespace)
+ ("type" . lsp-face-semhl-type)
+ ("struct" . lsp-face-semhl-struct)
+ ("class" . lsp-face-semhl-class)
+ ("interface" . lsp-face-semhl-interface)
+ ("enum" . lsp-face-semhl-enum)
+ ("typeParameter" . lsp-face-semhl-type-parameter)
+ ("function" . lsp-face-semhl-function)
+ ("method" . lsp-face-semhl-method)
+ ("member" . lsp-face-semhl-member)
+ ("property" . lsp-face-semhl-property)
+ ("event" . lsp-face-semhl-event)
+ ("macro" . lsp-face-semhl-macro)
+ ("variable" . lsp-face-semhl-variable)
+ ("parameter" . lsp-face-semhl-parameter)
+ ("label" . lsp-face-semhl-label)
+ ("enumConstant" . lsp-face-semhl-constant)
+ ("enumMember" . lsp-face-semhl-constant)
+ ("dependent" . lsp-face-semhl-type)
+ ("concept" . lsp-face-semhl-interface))
+ "Faces to use for semantic tokens.")
+
+(defvar lsp-semantic-token-modifier-faces
+ '(("declaration" . lsp-face-semhl-interface)
+ ("definition" . lsp-face-semhl-definition)
+ ("implementation" . lsp-face-semhl-implementation)
+ ("readonly" . lsp-face-semhl-constant)
+ ("static" . lsp-face-semhl-static)
+ ("deprecated" . lsp-face-semhl-deprecated)
+ ("abstract" . lsp-face-semhl-keyword)
+ ("async" . lsp-face-semhl-macro)
+ ("modification" . lsp-face-semhl-operator)
+ ("documentation" . lsp-face-semhl-comment)
+ ("defaultLibrary" . lsp-face-semhl-default-library))
+ "Semantic tokens modifier faces.
+Faces to use for semantic token modifiers if
+`lsp-semantic-tokens-apply-modifiers' is non-nil.")
+
+(defvar lsp-semantic-tokens-capabilities
+ `((semanticTokens
+ . ((dynamicRegistration . t)
+ (requests . ((range . t) (full . t)))
+ (tokenModifiers . ,(if lsp-semantic-tokens-apply-modifiers
+ (apply 'vector (mapcar #'car lsp-semantic-token-modifier-faces))
+ []))
+ (tokenTypes . ,(apply 'vector (mapcar #'car lsp-semantic-token-faces)))
+ (formats . ["relative"])))))
+
+(defvar lsp--semantic-tokens-pending-full-token-requests '()
+ "Buffers which should have their semantic tokens refreshed on idle.
+
+This is an alist of the form ((buffer_i . fontify_immediately_i) ...); entries
+with fontify_immediately set to t will immediately refontify once their
+token request is answered.")
+
+;; NOTE: doesn't keep track of outstanding requests, so might still produce large latency outliers
+;; if the language server doesn't process all outstanding token requests within one lsp-idle-delay
+(defcustom lsp-semantic-tokens-max-concurrent-idle-requests 1
+ "Maximum number of on-idle token requests to be dispatched simultaneously."
+ :group 'lsp-semantic-tokens
+ :type 'integer)
+
+(defvar lsp--semantic-tokens-idle-timer nil)
+
+(defun lsp--semantic-tokens-process-pending-requests ()
+ (let ((fuel lsp-semantic-tokens-max-concurrent-idle-requests))
+ (while (and lsp--semantic-tokens-pending-full-token-requests (> fuel 0))
+ (-let (((buffer . fontify-immediately) (pop lsp--semantic-tokens-pending-full-token-requests)))
+ (when (buffer-live-p buffer)
+ (setq fuel (1- fuel))
+ (with-current-buffer buffer
+ (lsp--semantic-tokens-request nil fontify-immediately))))))
+ (unless lsp--semantic-tokens-pending-full-token-requests
+ (cancel-timer lsp--semantic-tokens-idle-timer)
+ (setq lsp--semantic-tokens-idle-timer nil)))
+
+(defun lsp--semantic-tokens-sort-pending-requests (pending-requests)
+ ;; service currently visible buffers first, otherwise prefer immediate-fontification requests
+ (-sort (lambda (entry-a entry-b)
+ (let ((a-hidden (eq nil (get-buffer-window (car entry-a))))
+ (b-hidden (eq nil (get-buffer-window (car entry-b)))))
+ (cond ((and b-hidden (not a-hidden)) t) ; sort a before b
+ ((and a-hidden (not b-hidden)) nil) ; sort b before a
+ ((and (not (cdr entry-a)) (cdr entry-b)) nil) ; otherwise sort b before a only if b is immediate and a is not
+ (t t))))
+ (--filter (buffer-live-p (car it)) pending-requests)))
+
+(defun lsp--semantic-tokens-request-full-token-set-when-idle (buffer fontify-immediately)
+ "Request full token set after an idle timeout of `lsp-idle-delay'.
+
+If FONTIFY-IMMEDIATELY is non-nil, fontification will be performed immediately
+ once the corresponding response is received."
+ (let ((do-fontify-immediately (or fontify-immediately
+ (cdr (assoc buffer lsp--semantic-tokens-pending-full-token-requests)))))
+ (setq lsp--semantic-tokens-pending-full-token-requests
+ (lsp--semantic-tokens-sort-pending-requests
+ (cons (cons buffer do-fontify-immediately)
+ (--remove (eq buffer (car it)) lsp--semantic-tokens-pending-full-token-requests)))))
+ (unless lsp--semantic-tokens-idle-timer
+ (setq lsp--semantic-tokens-idle-timer
+ (run-with-idle-timer lsp-idle-delay t #'lsp--semantic-tokens-process-pending-requests))))
+
+(defun lsp--semantic-tokens-refresh-if-enabled (buffer)
+ (when (buffer-local-value 'lsp-semantic-tokens-mode buffer)
+ (lsp--semantic-tokens-request-full-token-set-when-idle buffer t)))
+
+(defvar-local lsp--semantic-tokens-cache nil
+ "Previously returned token set.
+
+When non-nil, `lsp--semantic-tokens-cache' should adhere to the
+following lsp-interface:
+`(_SemanticTokensCache
+ (:_documentVersion)
+ (:response :_region :_truncated))'.")
+
+(defsubst lsp--semantic-tokens-putcache (k v)
+ "Set key K of `lsp--semantic-tokens-cache' to V."
+ (setq lsp--semantic-tokens-cache
+ (plist-put lsp--semantic-tokens-cache k v)))
+
+(defvar-local lsp--semantic-tokens-teardown nil)
+
+(defun lsp--semantic-tokens-ingest-range-response (response)
+ "Handle RESPONSE to semanticTokens/range request."
+ (lsp--semantic-tokens-putcache :response response)
+ (cl-assert (plist-get lsp--semantic-tokens-cache :_region))
+ (lsp--semantic-tokens-request-full-token-set-when-idle (current-buffer) nil))
+
+(defun lsp--semantic-tokens-ingest-full-response (response)
+ "Handle RESPONSE to semanticTokens/full request."
+ (lsp--semantic-tokens-putcache :response response)
+ (cl-assert (not (plist-get lsp--semantic-tokens-cache :_region))))
+
+(defsubst lsp--semantic-tokens-apply-delta-edits (old-data edits)
+ "Apply EDITS obtained from full/delta request to OLD-DATA."
+ (let* ((old-token-count (length old-data))
+ (old-token-index 0)
+ (substrings))
+ (cl-loop
+ for edit across edits
+ do
+ (when (< old-token-index (lsp-get edit :start))
+ (push (substring old-data old-token-index (lsp-get edit :start)) substrings))
+ (push (lsp-get edit :data) substrings)
+ (setq old-token-index (+ (lsp-get edit :start) (lsp-get edit :deleteCount)))
+ finally do (push (substring old-data old-token-index old-token-count) substrings))
+ (apply #'vconcat (nreverse substrings))))
+
+(defun lsp--semantic-tokens-ingest-full/delta-response (response)
+ "Handle RESPONSE to semanticTokens/full/delta request."
+ (if (lsp-get response :edits)
+ (let ((old-data (--> lsp--semantic-tokens-cache (plist-get it :response) (lsp-get it :data))))
+ (cl-assert (not (plist-get lsp--semantic-tokens-cache :_region)))
+ (when old-data
+ (lsp--semantic-tokens-putcache
+ :response (lsp-put response
+ :data (lsp--semantic-tokens-apply-delta-edits
+ old-data (lsp-get response :edits))))))
+ ;; server decided to send full response instead
+ (lsp--semantic-tokens-ingest-full-response response)))
+
+
+(defun lsp--semantic-tokens-request (region fontify-immediately)
+ "Send semantic tokens request to the language server.
+
+A full/delta request will be sent if delta requests are supported by
+the language server, allowed via `lsp-semantic-tokens-allow-delta-requests',
+and if a full set of tokens had previously been received.
+Otherwise, a ranged request will be dispatched if REGION is non-nil,
+ranged requests are supported by the language server, and allowed via
+`lsp-semantic-tokens-allow-delta-requests'. In all other cases, a full
+tokens request will be dispatched.
+
+If FONTIFY-IMMEDIATELY is non-nil, fontification will be performed immediately
+ upon receiving the response."
+ (let ((request-type "textDocument/semanticTokens/full")
+ (request `(:textDocument ,(lsp--text-document-identifier)))
+ (response-handler nil)
+ (final-region nil))
+ (cond
+ ((and lsp-semantic-tokens-allow-delta-requests
+ (lsp-feature? "textDocument/semanticTokensFull/Delta")
+ (--> lsp--semantic-tokens-cache
+ (plist-get it :response)
+ (and (lsp-get it :resultId) (lsp-get it :data)
+ (not (plist-get lsp--semantic-tokens-cache :_region)))))
+ (setq request-type "textDocument/semanticTokens/full/delta")
+ (setq response-handler #'lsp--semantic-tokens-ingest-full/delta-response)
+ (setq request
+ (plist-put request :previousResultId
+ (lsp-get (plist-get lsp--semantic-tokens-cache :response) :resultId))))
+ ((and lsp-semantic-tokens-allow-ranged-requests region
+ (lsp-feature? "textDocument/semanticTokensRangeProvider"))
+ (setq request-type "textDocument/semanticTokens/range")
+ (setq final-region region)
+ (setq request
+ (plist-put request :range (lsp--region-to-range (car final-region) (cdr final-region))))
+ (setq response-handler #'lsp--semantic-tokens-ingest-range-response))
+ (t (setq response-handler #'lsp--semantic-tokens-ingest-full-response)))
+ (lsp-request-async
+ request-type request
+ (lambda (response)
+ (lsp--semantic-tokens-putcache :_documentVersion lsp--cur-version)
+ (lsp--semantic-tokens-putcache :_region final-region)
+ (funcall response-handler response)
+ (when (or fontify-immediately (plist-get lsp--semantic-tokens-cache :_truncated)) (font-lock-flush)))
+ :error-handler ;; buffer is not captured in `error-handler', it is in `callback'
+ (let ((buf (current-buffer)))
+ (lambda (&rest _)
+ (when (buffer-live-p buf)
+ (lsp--semantic-tokens-request-full-token-set-when-idle buf t))))
+ :mode 'tick
+ :cancel-token (format "semantic-tokens-%s" (lsp--buffer-uri)))))
+
+
+(defun lsp-semantic-tokens--fontify (old-fontify-region beg-orig end-orig &optional loudly)
+ "Apply fonts to retrieved semantic tokens.
+OLD-FONTIFY-REGION is the underlying region fontification function,
+e.g., `font-lock-fontify-region'.
+BEG-ORIG and END-ORIG deliminate the requested fontification region and maybe
+modified by OLD-FONTIFY-REGION.
+LOUDLY will be forwarded to OLD-FONTIFY-REGION as-is."
+ ;; TODO: support multiple language servers per buffer?
+ (let ((faces (seq-some #'lsp--workspace-semantic-tokens-faces lsp--buffer-workspaces))
+ (modifier-faces
+ (when lsp-semantic-tokens-apply-modifiers
+ (seq-some #'lsp--workspace-semantic-tokens-modifier-faces lsp--buffer-workspaces)))
+ old-bounds
+ beg end)
+ (cond
+ ((or (eq nil faces)
+ (eq nil lsp--semantic-tokens-cache)
+ (eq nil (plist-get lsp--semantic-tokens-cache :response)))
+ ;; default to non-semantic highlighting until first response has arrived
+ (funcall old-fontify-region beg-orig end-orig loudly))
+ ((not (= lsp--cur-version (plist-get lsp--semantic-tokens-cache :_documentVersion)))
+ ;; delay fontification until we have fresh tokens
+ '(jit-lock-bounds 0 . 0))
+ (t
+ (setq old-bounds (funcall old-fontify-region beg-orig end-orig loudly))
+ ;; this is to prevent flickering when semantic token highlighting
+ ;; is layered on top of, e.g., tree-sitter-hl, or clojure-mode's syntax highlighting.
+ (setq beg (min beg-orig (cadr old-bounds))
+ end (max end-orig (cddr old-bounds)))
+ ;; if we're using the response to a ranged request, we'll only be able to fontify within
+ ;; that range (and hence shouldn't clear any highlights outside of that range)
+ (let ((token-region (plist-get lsp--semantic-tokens-cache :_region)))
+ (if token-region
+ (progn
+ (lsp--semantic-tokens-putcache :_truncated (or (< beg (car token-region))
+ (> end (cdr token-region))))
+ (setq beg (max beg (car token-region)))
+ (setq end (min end (cdr token-region))))
+ (lsp--semantic-tokens-putcache :_truncated nil)))
+ (-let* ((inhibit-field-text-motion t)
+ (data (lsp-get (plist-get lsp--semantic-tokens-cache :response) :data))
+ (i0 0)
+ (i-max (1- (length data)))
+ (current-line 1)
+ (line-delta)
+ (column 0)
+ (face)
+ (line-start-pos)
+ (line-min)
+ (line-max-inclusive)
+ (text-property-beg)
+ (text-property-end))
+ (save-mark-and-excursion
+ (save-restriction
+ (widen)
+ (goto-char beg)
+ (goto-char (line-beginning-position))
+ (setq line-min (line-number-at-pos))
+ (with-silent-modifications
+ (goto-char end)
+ (goto-char (line-end-position))
+ (setq line-max-inclusive (line-number-at-pos))
+ (forward-line (- line-min line-max-inclusive))
+ (let ((skip-lines (- line-min current-line)))
+ (while (and (<= i0 i-max) (< (aref data i0) skip-lines))
+ (setq skip-lines (- skip-lines (aref data i0)))
+ (setq i0 (+ i0 5)))
+ (setq current-line (- line-min skip-lines)))
+ (forward-line (- current-line line-min))
+ (setq line-start-pos (point))
+ (cl-loop
+ for i from i0 to i-max by 5 do
+ (setq line-delta (aref data i))
+ (unless (= line-delta 0)
+ (forward-line line-delta)
+ (setq line-start-pos (point))
+ (setq column 0)
+ (setq current-line (+ current-line line-delta)))
+ (setq column (+ column (aref data (1+ i))))
+ (setq face (aref faces (aref data (+ i 3))))
+ (setq text-property-beg (+ line-start-pos column))
+ (setq text-property-end (+ text-property-beg (aref data (+ i 2))))
+ (when face
+ (put-text-property text-property-beg text-property-end 'face face))
+ (cl-loop for j from 0 to (1- (length modifier-faces)) do
+ (when (and (aref modifier-faces j)
+ (> (logand (aref data (+ i 4)) (lsh 1 j)) 0))
+ (add-face-text-property text-property-beg text-property-end
+ (aref modifier-faces j))))
+ when (> current-line line-max-inclusive) return nil)))))
+ `(jit-lock-bounds ,beg . ,end)))))
+
+(defun lsp-semantic-tokens--request-update ()
+ "Request semantic-tokens update."
+ ;; when dispatching ranged requests, we'll over-request by several chunks in both directions,
+ ;; which should minimize those occasions where font-lock region extension extends beyond the
+ ;; region covered by our freshly requested tokens (see lsp-mode issue #3154), while still limiting
+ ;; requests to fairly small regions even if the underlying buffer is large
+ (lsp--semantic-tokens-request
+ (cons (max (point-min) (- (window-start) (* 5 jit-lock-chunk-size)))
+ (min (point-max) (+ (window-end) (* 5 jit-lock-chunk-size)))) t))
+
+(defun lsp--semantic-tokens-as-defined-by-workspace (workspace)
+ "Return plist of token-types and token-modifiers defined by WORKSPACE,
+or nil if none are defined."
+ (when-let ((token-capabilities
+ (or
+ (-some->
+ (lsp--registered-capability "textDocument/semanticTokens")
+ (lsp--registered-capability-options))
+ (lsp:server-capabilities-semantic-tokens-provider?
+ (lsp--workspace-server-capabilities workspace)))))
+ (-let* (((&SemanticTokensOptions :legend) token-capabilities))
+ `(:token-types ,(lsp:semantic-tokens-legend-token-types legend)
+ :token-modifiers ,(lsp:semantic-tokens-legend-token-modifiers legend)))))
+
+(defun lsp-semantic-tokens-suggest-overrides ()
+ "Suggest face overrides that best match the faces
+chosen by `font-lock-fontify-region'."
+ (interactive)
+ (-when-let* ((token-info (-some #'lsp--semantic-tokens-as-defined-by-workspace lsp--buffer-workspaces))
+ ((&plist :token-types token-types :token-modifiers token-modifiers) token-info))
+ (let* ((tokens (lsp-request
+ "textDocument/semanticTokens/full"
+ `(:textDocument, (lsp--text-document-identifier))))
+ (inhibit-field-text-motion t)
+ (data (lsp-get tokens :data))
+ (associated-faces '())
+ (line-delta)
+ ;; KLUDGE: clear cache so our font-lock advice won't apply semantic-token faces
+ (old-cache lsp--semantic-tokens-cache)
+ (face-or-faces))
+ (setq lsp--semantic-tokens-cache nil)
+ (save-restriction
+ (save-excursion
+ (widen)
+ (font-lock-fontify-region (point-min) (point-max) t)
+ (save-mark-and-excursion
+ (save-restriction
+ (widen)
+ (goto-char (point-min))
+ (cl-loop
+ for i from 0 to (1- (length data)) by 5 do
+ (setq line-delta (aref data i))
+ (unless (= line-delta 0) (forward-line line-delta))
+ (forward-char (aref data (+ i 1)))
+ (setq face-or-faces (get-text-property (point) 'face))
+ ;; TODO: consider modifiers?
+ (when face-or-faces
+ (--each (if (listp face-or-faces) face-or-faces (list face-or-faces))
+ (cl-pushnew `(,(aref data (+ i 3)) . ,it) associated-faces :test #'equal))))
+ (setq lsp--semantic-tokens-cache old-cache)
+ (font-lock-flush)))))
+ (switch-to-buffer (get-buffer-create "*Suggested Overrides*"))
+ (insert "(")
+ ;; TODO: sort alternatives by frequency
+ (--each-indexed (-group-by #'car associated-faces)
+ (insert (if (= it-index 0) "(" "\n ("))
+ (insert (format "%s . " (aref token-types (car it))))
+ (--each-indexed (mapcar #'cdr (cdr it))
+ (insert (if (= it-index 0) (format "%s)" (prin1-to-string it))
+ (format " ; Alternative: %s" (prin1-to-string it))))))
+ (insert ")"))))
+
+(declare-function tree-sitter-hl-mode "ext:tree-sitter-hl")
+
+(with-eval-after-load 'tree-sitter-hl
+ (add-hook
+ 'tree-sitter-hl-mode-hook
+ (lambda ()
+ (when (and lsp-mode lsp--semantic-tokens-teardown
+ (boundp 'tree-sitter-hl-mode) tree-sitter-hl-mode)
+ (lsp-warn "It seems you have configured tree-sitter-hl to activate after lsp-mode.
+To prevent tree-sitter-hl from overriding lsp-mode's semantic token highlighting, lsp-mode
+will now disable both semantic highlighting and tree-sitter-hl mode and subsequently re-enable both,
+starting with tree-sitter-hl-mode.
+
+Please adapt your config to prevent unnecessary mode reinitialization in the future.")
+ (tree-sitter-hl-mode -1)
+ (funcall lsp--semantic-tokens-teardown)
+ (setq lsp--semantic-tokens-teardown nil)
+ (tree-sitter-hl-mode t)
+ (lsp--semantic-tokens-initialize-buffer)))))
+
+;;;###autoload
+(defun lsp--semantic-tokens-initialize-buffer ()
+ "Initialize the buffer for semantic tokens.
+IS-RANGE-PROVIDER is non-nil when server supports range requests."
+ (let* ((old-extend-region-functions font-lock-extend-region-functions)
+ ;; make sure font-lock always fontifies entire lines (TODO: do we also have
+ ;; to change some jit-lock-...-region functions/variables?)
+ (new-extend-region-functions
+ (if (memq 'font-lock-extend-region-wholelines old-extend-region-functions)
+ old-extend-region-functions
+ (cons 'font-lock-extend-region-wholelines old-extend-region-functions)))
+ (buffer (current-buffer)))
+ (setq lsp--semantic-tokens-cache nil)
+ (setq font-lock-extend-region-functions new-extend-region-functions)
+ (add-function :around (local 'font-lock-fontify-region-function) #'lsp-semantic-tokens--fontify)
+ (add-hook 'lsp-on-change-hook #'lsp-semantic-tokens--request-update nil t)
+ (lsp-semantic-tokens--request-update)
+ (setq lsp--semantic-tokens-teardown
+ (lambda ()
+ (setq lsp--semantic-tokens-pending-full-token-requests
+ (--remove (eq buffer (car it)) lsp--semantic-tokens-pending-full-token-requests))
+ (setq font-lock-extend-region-functions old-extend-region-functions)
+ (setq lsp--semantic-tokens-cache nil)
+ (remove-function (local 'font-lock-fontify-region-function)
+ #'lsp-semantic-tokens--fontify)
+ (remove-hook 'lsp-on-change-hook #'lsp-semantic-tokens--request-update t)))))
+
+(defun lsp--semantic-tokens-build-face-map (identifiers faces category varname)
+ "Build map of FACES for IDENTIFIERS using CATEGORY and VARNAME."
+ (apply 'vector
+ (mapcar (lambda (id)
+ (let ((maybe-face (cdr (assoc id faces))))
+ (when (and lsp-semantic-tokens-warn-on-missing-face (not maybe-face))
+ (lsp-warn "No face has been associated to the %s '%s': consider adding a corresponding definition to %s"
+ category id varname)) maybe-face)) identifiers)))
+
+(defun lsp-semantic-tokens--replace-alist-values (a b)
+ "Replace alist A values with B ones where available."
+ (-map
+ (-lambda ((ak . av))
+ (cons ak (alist-get ak b av nil #'string=)))
+ a))
+
+(defun lsp-semantic-tokens--type-faces-for (client)
+ "Return the semantic token type faces for CLIENT."
+ (lsp-semantic-tokens--replace-alist-values lsp-semantic-token-faces
+ (plist-get (lsp--client-semantic-tokens-faces-overrides client) :types)))
+
+(defun lsp-semantic-tokens--modifier-faces-for (client)
+ "Return the semantic token type faces for CLIENT."
+ (lsp-semantic-tokens--replace-alist-values lsp-semantic-token-modifier-faces
+ (plist-get (lsp--client-semantic-tokens-faces-overrides client) :modifiers)))
+
+(defun lsp--semantic-tokens-on-refresh (workspace)
+ "Clear semantic tokens within all buffers of WORKSPACE,
+refresh in currently active buffer."
+ (cl-assert (not (eq nil workspace)))
+ (when lsp-semantic-tokens-honor-refresh-requests
+ (cl-loop
+ for ws-buffer in (lsp--workspace-buffers workspace) do
+ (let ((fontify-immediately (equal (current-buffer) ws-buffer)))
+ (with-current-buffer ws-buffer (lsp--semantic-tokens-request nil fontify-immediately))))))
+
+;;;###autoload
+(defun lsp--semantic-tokens-initialize-workspace (workspace)
+ "Initialize semantic tokens for WORKSPACE."
+ (cl-assert workspace)
+ (-let (((&plist :token-types types :token-modifiers modifiers)
+ (lsp--semantic-tokens-as-defined-by-workspace workspace))
+ (client (lsp--workspace-client workspace)))
+ (setf (lsp--workspace-semantic-tokens-faces workspace)
+ (lsp--semantic-tokens-build-face-map
+ types (lsp-semantic-tokens--type-faces-for client)
+ "semantic token" "lsp-semantic-token-faces"))
+ (setf (lsp--workspace-semantic-tokens-modifier-faces workspace)
+ (lsp--semantic-tokens-build-face-map
+ modifiers (lsp-semantic-tokens--modifier-faces-for client)
+ "semantic token modifier" "lsp-semantic-token-modifier-faces"))))
+
+;;;###autoload
+(defun lsp-semantic-tokens--warn-about-deprecated-setting ()
+ "Warn about deprecated semantic highlighting variable."
+ (when (boundp 'lsp-semantic-highlighting)
+ (pcase lsp-semantic-highlighting
+ (:semantic-tokens
+ (lsp-warn "It seems you wish to use semanticTokens-based
+ highlighting. To do so, please remove any references to the
+ deprecated variable `lsp-semantic-highlighting' from your
+ configuration and set `lsp-semantic-tokens-enable' to `t'
+ instead.")
+ (setq lsp-semantic-tokens-enable t))
+ ((or :immediate :deferred)
+ (lsp-warn "It seems you wish to use Theia-based semantic
+ highlighting. This protocol has been superseded by the
+ semanticTokens protocol specified by LSP v3.16 and is no longer
+ supported by lsp-mode. If your language server provides
+ semanticToken support, please set
+ `lsp-semantic-tokens-enable' to `t' to use it.")))))
+
+;;;###autoload
+(defun lsp-semantic-tokens--enable ()
+ "Enable semantic tokens mode."
+ (when (and lsp-semantic-tokens-enable
+ (lsp-feature? "textDocument/semanticTokens"))
+ (lsp-semantic-tokens--warn-about-deprecated-setting)
+ (lsp-semantic-tokens-mode 1)))
+
+(defun lsp-semantic-tokens--disable ()
+ "Disable semantic tokens mode."
+ (lsp-semantic-tokens-mode -1))
+
+;;;###autoload
+(define-minor-mode lsp-semantic-tokens-mode
+ "Toggle semantic-tokens support."
+ :group 'lsp-semantic-tokens
+ :global nil
+ (cond
+ (lsp-semantic-tokens-mode
+ (add-hook 'lsp-configure-hook #'lsp-semantic-tokens--enable nil t)
+ (add-hook 'lsp-unconfigure-hook #'lsp-semantic-tokens--disable nil t)
+ (mapc #'lsp--semantic-tokens-initialize-workspace
+ (lsp--find-workspaces-for "textDocument/semanticTokens"))
+ (lsp--semantic-tokens-initialize-buffer))
+ (t
+ (remove-hook 'lsp-configure-hook #'lsp-semantic-tokens--enable t)
+ (remove-hook 'lsp-unconfigure-hook #'lsp-semantic-tokens--disable t)
+ (when lsp--semantic-tokens-teardown
+ (funcall lsp--semantic-tokens-teardown))
+ (lsp-semantic-tokens--request-update)
+ (setq lsp--semantic-tokens-cache nil
+ lsp--semantic-tokens-teardown nil))))
+
+;; debugging helpers
+(defun lsp--semantic-tokens-verify ()
+ "Store current token set and compare with the response to a full token request."
+ (interactive)
+ (let ((old-tokens (--> lsp--semantic-tokens-cache (plist-get it :response) (lsp-get it :data)))
+ (old-version (--> lsp--semantic-tokens-cache (plist-get it :_documentVersion))))
+ (if (not (equal lsp--cur-version old-version))
+ (message "Stored documentVersion %d differs from current version %d" old-version lsp--cur-version)
+ (lsp-request-async
+ "textDocument/semanticTokens/full" `(:textDocument ,(lsp--text-document-identifier))
+ (lambda (response)
+ (let ((new-tokens (lsp-get response :data)))
+ (if (equal old-tokens new-tokens)
+ (message "New tokens (total count %d) are identical to previously held token set"
+ (length new-tokens))
+ (message "Newly returned tokens differ from old token set")
+ (print old-tokens)
+ (print new-tokens))))
+ :mode 'tick
+ :cancel-token (format "semantic-tokens-%s" (lsp--buffer-uri))))))
+
+(defvar-local lsp-semantic-tokens--log '())
+
+(defvar-local lsp-semantic-tokens--prev-response nil)
+
+(defun lsp-semantic-tokens--log-buffer-contents (tag)
+ "Log buffer contents for TAG."
+ (save-restriction
+ (save-excursion
+ (widen) (push `(:tag ,tag
+ :buffer-contents ,(buffer-substring (point-min) (point-max))
+ :prev-response ,lsp-semantic-tokens--prev-response)
+ lsp-semantic-tokens--log))))
+
+(defun lsp-semantic-tokens-enable-log ()
+ "Enable logging of intermediate fontification states.
+
+This is a debugging tool, and may incur significant performance penalties."
+ (setq lsp-semantic-tokens--log '())
+ (defadvice lsp-semantic-tokens--fontify (around advice-tokens-fontify activate)
+ (lsp-semantic-tokens--log-buffer-contents 'before)
+ (let ((result ad-do-it))
+ (lsp-semantic-tokens--log-buffer-contents 'after)
+ result))
+ (defadvice lsp--semantic-tokens-ingest-full/delta-response
+ (before log-delta-response (response) activate)
+ (setq lsp-semantic-tokens--prev-response `(:request-type "delta"
+ :response ,response
+ :version ,lsp--cur-version)))
+ (defadvice lsp--semantic-tokens-ingest-full-response
+ (before log-full-response (response) activate)
+ (setq lsp-semantic-tokens--prev-response `(:request-type "full"
+ :response ,response
+ :version ,lsp--cur-version)))
+ (defadvice lsp--semantic-tokens-ingest-range-response
+ (before log-range-response (response) activate)
+ (setq lsp-semantic-tokens--prev-response `(:request-type "range"
+ :response ,response
+ :version ,lsp--cur-version))))
+
+(defun lsp-semantic-tokens-disable-log ()
+ "Disable logging of intermediate fontification states."
+ (ad-unadvise 'lsp-semantic-tokens--fontify)
+ (ad-unadvise 'lsp--semantic-tokens-ingest-full/delta-response)
+ (ad-unadvise 'lsp--semantic-tokens-ingest-full-response)
+ (ad-unadvise 'lsp--semantic-tokens-ingest-range-response))
+
+(declare-function htmlize-buffer "ext:htmlize")
+
+(defun lsp-semantic-tokens-export-log ()
+ "Write HTML-formatted snapshots of previous fontification results to /tmp."
+ (require 'htmlize)
+ (let* ((outdir (f-join "/tmp" "semantic-token-snapshots"))
+ (progress-reporter
+ (make-progress-reporter
+ (format "Writing buffer snapshots to %s..." outdir)
+ 0 (length lsp-semantic-tokens--log))))
+ (f-mkdir outdir)
+ (--each-indexed (reverse lsp-semantic-tokens--log)
+ (-let* (((&plist :tag tag
+ :buffer-contents buffer-contents
+ :prev-response prev-response) it)
+ (html-buffer))
+ ;; FIXME: doesn't update properly; sit-for helps... somewhat,
+ ;; but unreliably
+ (when (= (% it-index 5) 0)
+ (progress-reporter-update progress-reporter it-index)
+ (sit-for 0.01))
+ ;; we're emitting 2 snapshots (before & after) per update, so request
+ ;; parameters should only change on every 2nd invocation
+ (when (cl-evenp it-index)
+ (with-temp-buffer
+ (insert (prin1-to-string prev-response))
+ (write-file (f-join outdir (format "parameters_%d.el" (/ it-index 2))))))
+ (with-temp-buffer
+ (insert buffer-contents)
+ (setq html-buffer (htmlize-buffer))
+ (with-current-buffer html-buffer
+ ;; some configs such as emacs-doom may autoformat on save; switch to
+ ;; fundamental-mode to avoid this
+ (fundamental-mode)
+ (write-file (f-join outdir (format "buffer_%d_%s.html" (/ it-index 2) tag)))))
+ (kill-buffer html-buffer)))
+ (progress-reporter-done progress-reporter)))
+
+(lsp-consistency-check lsp-semantic-tokens)
+
+(provide 'lsp-semantic-tokens)
+;;; lsp-semantic-tokens.el ends here