diff options
Diffstat (limited to 'lisp/progmodes')
| -rw-r--r-- | lisp/progmodes/eglot.el | 65 | ||||
| -rw-r--r-- | lisp/progmodes/elisp-mode.el | 4 | ||||
| -rw-r--r-- | lisp/progmodes/etags-regen.el | 2 | ||||
| -rw-r--r-- | lisp/progmodes/etags.el | 10 | ||||
| -rw-r--r-- | lisp/progmodes/make-mode.el | 18 | ||||
| -rw-r--r-- | lisp/progmodes/project.el | 147 | ||||
| -rw-r--r-- | lisp/progmodes/python.el | 40 | ||||
| -rw-r--r-- | lisp/progmodes/xref.el | 23 |
8 files changed, 200 insertions, 109 deletions
diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index 80099a26ee8..4752b0100d9 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el | |||
| @@ -282,7 +282,8 @@ automatically)." | |||
| 282 | . ,(eglot-alternatives | 282 | . ,(eglot-alternatives |
| 283 | '(("solargraph" "socket" "--port" :autoport) "ruby-lsp"))) | 283 | '(("solargraph" "socket" "--port" :autoport) "ruby-lsp"))) |
| 284 | (haskell-mode | 284 | (haskell-mode |
| 285 | . ("haskell-language-server-wrapper" "--lsp")) | 285 | . ,(eglot-alternatives |
| 286 | '(("haskell-language-server-wrapper" "--lsp") "static-ls"))) | ||
| 286 | (elm-mode . ("elm-language-server")) | 287 | (elm-mode . ("elm-language-server")) |
| 287 | (mint-mode . ("mint" "ls")) | 288 | (mint-mode . ("mint" "ls")) |
| 288 | ((kotlin-mode kotlin-ts-mode) . ("kotlin-language-server")) | 289 | ((kotlin-mode kotlin-ts-mode) . ("kotlin-language-server")) |
| @@ -308,7 +309,7 @@ automatically)." | |||
| 308 | (racket-mode . ("racket" "-l" "racket-langserver")) | 309 | (racket-mode . ("racket" "-l" "racket-langserver")) |
| 309 | ((latex-mode plain-tex-mode context-mode texinfo-mode bibtex-mode tex-mode) | 310 | ((latex-mode plain-tex-mode context-mode texinfo-mode bibtex-mode tex-mode) |
| 310 | . ,(eglot-alternatives '("digestif" "texlab"))) | 311 | . ,(eglot-alternatives '("digestif" "texlab"))) |
| 311 | (erlang-mode . ("erlang_ls" "--transport" "stdio")) | 312 | (erlang-mode . ("elp" "server")) |
| 312 | (wat-mode . ("wat_server")) | 313 | (wat-mode . ("wat_server")) |
| 313 | ((yaml-ts-mode yaml-mode) . ("yaml-language-server" "--stdio")) | 314 | ((yaml-ts-mode yaml-mode) . ("yaml-language-server" "--stdio")) |
| 314 | ((toml-ts-mode conf-toml-mode) . ("tombi" "lsp")) | 315 | ((toml-ts-mode conf-toml-mode) . ("tombi" "lsp")) |
| @@ -1438,6 +1439,12 @@ PRESERVE-BUFFERS as in `eglot-shutdown', which see." | |||
| 1438 | (maphash (lambda (f s) | 1439 | (maphash (lambda (f s) |
| 1439 | (when (eq s server) (remhash f eglot--servers-by-xrefed-file))) | 1440 | (when (eq s server) (remhash f eglot--servers-by-xrefed-file))) |
| 1440 | eglot--servers-by-xrefed-file) | 1441 | eglot--servers-by-xrefed-file) |
| 1442 | ;; Cleanup entries in 'flymake-list-only-diagnostics' | ||
| 1443 | (setq flymake-list-only-diagnostics | ||
| 1444 | (cl-delete-if | ||
| 1445 | (lambda (x) (eq server | ||
| 1446 | (get-text-property 0 'eglot--server (car x)))) | ||
| 1447 | flymake-list-only-diagnostics)) | ||
| 1441 | (cond ((eglot--shutdown-requested server) | 1448 | (cond ((eglot--shutdown-requested server) |
| 1442 | t) | 1449 | t) |
| 1443 | ((not (eglot--inhibit-autoreconnect server)) | 1450 | ((not (eglot--inhibit-autoreconnect server)) |
| @@ -2024,21 +2031,25 @@ according to `eglot-advertise-cancellation'.") | |||
| 2024 | (timeout-fn nil timeout-fn-supplied-p) | 2031 | (timeout-fn nil timeout-fn-supplied-p) |
| 2025 | (timeout nil timeout-supplied-p) | 2032 | (timeout nil timeout-supplied-p) |
| 2026 | hint | 2033 | hint |
| 2027 | &aux moreargs | 2034 | &aux moreargs id |
| 2028 | id (buf (current-buffer))) | 2035 | (buf (current-buffer)) |
| 2036 | (inflight eglot--inflight-async-requests)) | ||
| 2029 | "Like `jsonrpc-async-request', but for Eglot LSP requests. | 2037 | "Like `jsonrpc-async-request', but for Eglot LSP requests. |
| 2030 | SUCCESS-FN, ERROR-FN and TIMEOUT-FN run in buffer of call site. | 2038 | SUCCESS-FN, ERROR-FN and TIMEOUT-FN run in buffer of call site. |
| 2031 | HINT argument is a symbol passed as DEFERRED to `jsonrpc-async-request' | 2039 | HINT argument is a symbol passed as DEFERRED to `jsonrpc-async-request' |
| 2032 | and also used as a hint of the request cancellation mechanism (see | 2040 | and also used as a hint of the request cancellation mechanism (see |
| 2033 | `eglot-advertise-cancellation')." | 2041 | `eglot-advertise-cancellation')." |
| 2034 | (cl-labels | 2042 | (cl-labels |
| 2035 | ((clearing-fn (fn) | 2043 | ((wrapfn (fn) |
| 2036 | (lambda (&rest args) | 2044 | (lambda (&rest args) |
| 2037 | (eglot--when-live-buffer buf | 2045 | (eglot--when-live-buffer buf |
| 2038 | (when (and | 2046 | (cond (eglot-advertise-cancellation |
| 2039 | fn (memq id (cl-getf eglot--inflight-async-requests hint))) | 2047 | (when-let* ((tail (and fn (plist-member inflight hint)))) |
| 2040 | (apply fn args)) | 2048 | (when (memq id (cadr tail)) |
| 2041 | (cl-remf eglot--inflight-async-requests hint))))) | 2049 | (apply fn args)) |
| 2050 | (setf (cadr tail) (delete id (cadr tail))))) | ||
| 2051 | (t | ||
| 2052 | (apply fn args))))))) | ||
| 2042 | (eglot--cancel-inflight-async-requests (list hint)) | 2053 | (eglot--cancel-inflight-async-requests (list hint)) |
| 2043 | (when timeout-supplied-p | 2054 | (when timeout-supplied-p |
| 2044 | (setq moreargs (nconc `(:timeout ,timeout) moreargs))) | 2055 | (setq moreargs (nconc `(:timeout ,timeout) moreargs))) |
| @@ -2047,13 +2058,12 @@ and also used as a hint of the request cancellation mechanism (see | |||
| 2047 | (setq id | 2058 | (setq id |
| 2048 | (car (apply #'jsonrpc-async-request | 2059 | (car (apply #'jsonrpc-async-request |
| 2049 | server method params | 2060 | server method params |
| 2050 | :success-fn (clearing-fn success-fn) | 2061 | :success-fn (wrapfn success-fn) |
| 2051 | :error-fn (clearing-fn error-fn) | 2062 | :error-fn (wrapfn error-fn) |
| 2052 | :timeout-fn (clearing-fn timeout-fn) | 2063 | :timeout-fn (wrapfn timeout-fn) |
| 2053 | moreargs))) | 2064 | moreargs))) |
| 2054 | (when (and hint eglot-advertise-cancellation) | 2065 | (when (and hint eglot-advertise-cancellation) |
| 2055 | (push id | 2066 | (push id (plist-get inflight hint))) |
| 2056 | (plist-get eglot--inflight-async-requests hint))) | ||
| 2057 | id)) | 2067 | id)) |
| 2058 | 2068 | ||
| 2059 | (cl-defun eglot--delete-overlays (&optional (prop 'eglot--overlays)) | 2069 | (cl-defun eglot--delete-overlays (&optional (prop 'eglot--overlays)) |
| @@ -3422,11 +3432,8 @@ object. The originator of this \"push\" is usually either regular | |||
| 3422 | (with-current-buffer buffer | 3432 | (with-current-buffer buffer |
| 3423 | (if (and version (/= version eglot--docver)) | 3433 | (if (and version (/= version eglot--docver)) |
| 3424 | (cl-return-from eglot--flymake-handle-push)) | 3434 | (cl-return-from eglot--flymake-handle-push)) |
| 3425 | (setq | 3435 | ;; if no explicit version received, assume it's current. |
| 3426 | ;; if no explicit version received, assume it's current. | 3436 | (setq version eglot--docver) |
| 3427 | version eglot--docver | ||
| 3428 | flymake-list-only-diagnostics | ||
| 3429 | (assoc-delete-all path flymake-list-only-diagnostics)) | ||
| 3430 | (funcall then diagnostics)) | 3437 | (funcall then diagnostics)) |
| 3431 | (cl-loop | 3438 | (cl-loop |
| 3432 | for diag-spec across diagnostics | 3439 | for diag-spec across diagnostics |
| @@ -3437,12 +3444,13 @@ object. The originator of this \"push\" is usually either regular | |||
| 3437 | (flymake-make-diagnostic | 3444 | (flymake-make-diagnostic |
| 3438 | path (cons line char) nil | 3445 | path (cons line char) nil |
| 3439 | (eglot--flymake-diag-type severity) | 3446 | (eglot--flymake-diag-type severity) |
| 3440 | (list source code message)))) | 3447 | (list source code message) |
| 3448 | `((eglot-lsp-diag . ,diag-spec))))) | ||
| 3441 | into diags | 3449 | into diags |
| 3442 | finally | 3450 | finally |
| 3443 | (setq flymake-list-only-diagnostics | 3451 | (setf (alist-get (propertize path 'eglot--server server) |
| 3444 | (assoc-delete-all path flymake-list-only-diagnostics)) | 3452 | flymake-list-only-diagnostics nil nil #'equal) |
| 3445 | (push (cons path diags) flymake-list-only-diagnostics)))) | 3453 | diags)))) |
| 3446 | 3454 | ||
| 3447 | (cl-defun eglot--flymake-pull (&aux (server (eglot--current-server-or-lose)) | 3455 | (cl-defun eglot--flymake-pull (&aux (server (eglot--current-server-or-lose)) |
| 3448 | (origin (current-buffer))) | 3456 | (origin (current-buffer))) |
| @@ -3506,6 +3514,17 @@ MODE is like `eglot--flymake-report-1'." | |||
| 3506 | (pushed-outdated-p (and pushed-docver (< pushed-docver eglot--docver)))) | 3514 | (pushed-outdated-p (and pushed-docver (< pushed-docver eglot--docver)))) |
| 3507 | "Push previously collected diagnostics to `eglot--flymake-report-fn'. | 3515 | "Push previously collected diagnostics to `eglot--flymake-report-fn'. |
| 3508 | If KEEP, knowingly push a dummy do-nothing update." | 3516 | If KEEP, knowingly push a dummy do-nothing update." |
| 3517 | ;; Maybe hack in diagnostics we previously may have saved in | ||
| 3518 | ;; `flymake-list-only-diagnostics', pushed for this file before it was | ||
| 3519 | ;; visited (github#1531). | ||
| 3520 | (when-let* ((hack (and (<= eglot--docver 0) | ||
| 3521 | (null eglot--pushed-diagnostics) | ||
| 3522 | (cdr (assoc (buffer-file-name) | ||
| 3523 | flymake-list-only-diagnostics))))) | ||
| 3524 | (cl-loop | ||
| 3525 | for x in hack | ||
| 3526 | collect (alist-get 'eglot-lsp-diag (flymake-diagnostic-data x)) into res | ||
| 3527 | finally (setq eglot--pushed-diagnostics `(,(vconcat res) ,eglot--docver)))) | ||
| 3509 | (eglot--widening | 3528 | (eglot--widening |
| 3510 | (if (and (null eglot--pulled-diagnostics) pushed-outdated-p) | 3529 | (if (and (null eglot--pulled-diagnostics) pushed-outdated-p) |
| 3511 | ;; Here, we don't have anything interesting to give to Flymake. | 3530 | ;; Here, we don't have anything interesting to give to Flymake. |
diff --git a/lisp/progmodes/elisp-mode.el b/lisp/progmodes/elisp-mode.el index c4fb6946aeb..f5c3dc3fbb2 100644 --- a/lisp/progmodes/elisp-mode.el +++ b/lisp/progmodes/elisp-mode.el | |||
| @@ -1783,7 +1783,9 @@ and `eval-expression-print-level'. | |||
| 1783 | (funcall | 1783 | (funcall |
| 1784 | (syntax-propertize-rules | 1784 | (syntax-propertize-rules |
| 1785 | (emacs-lisp-byte-code-comment-re | 1785 | (emacs-lisp-byte-code-comment-re |
| 1786 | (1 (prog1 "< b" (elisp--byte-code-comment end (point)))))) | 1786 | (1 (prog1 "< b" |
| 1787 | (goto-char (match-end 2)) | ||
| 1788 | (elisp--byte-code-comment end (point)))))) | ||
| 1787 | start end)) | 1789 | start end)) |
| 1788 | 1790 | ||
| 1789 | ;;;###autoload | 1791 | ;;;###autoload |
diff --git a/lisp/progmodes/etags-regen.el b/lisp/progmodes/etags-regen.el index b6adca8af7a..3d3ddc0521f 100644 --- a/lisp/progmodes/etags-regen.el +++ b/lisp/progmodes/etags-regen.el | |||
| @@ -348,7 +348,7 @@ File extensions to generate the tags for." | |||
| 348 | 348 | ||
| 349 | (defun etags-regen--build-program-options (ctags-p) | 349 | (defun etags-regen--build-program-options (ctags-p) |
| 350 | (when (and etags-regen-regexp-alist ctags-p) | 350 | (when (and etags-regen-regexp-alist ctags-p) |
| 351 | (user-error "etags-regen-regexp-alist is not supported with Ctags")) | 351 | (user-error "etags-regen-regexp-alist not supported with Ctags; to use this option, customize `etags-regen-program'")) |
| 352 | (nconc | 352 | (nconc |
| 353 | (mapcan | 353 | (mapcan |
| 354 | (lambda (group) | 354 | (lambda (group) |
diff --git a/lisp/progmodes/etags.el b/lisp/progmodes/etags.el index 79cfb91caa9..f7532fce6b1 100644 --- a/lisp/progmodes/etags.el +++ b/lisp/progmodes/etags.el | |||
| @@ -2114,8 +2114,14 @@ file name, add `tag-partial-file-name-match-p' to the list value.") | |||
| 2114 | :type 'boolean | 2114 | :type 'boolean |
| 2115 | :version "28.1") | 2115 | :version "28.1") |
| 2116 | 2116 | ||
| 2117 | ;;;###autoload | 2117 | ;;;###autoload (defun etags--xref-backend () |
| 2118 | (defun etags--xref-backend () 'etags) | 2118 | ;;;###autoload (when (or tags-table-list tags-file-name) |
| 2119 | ;;;###autoload (load "etags") | ||
| 2120 | ;;;###autoload 'etags)) | ||
| 2121 | |||
| 2122 | (defun etags--xref-backend () | ||
| 2123 | (when (or tags-table-list tags-file-name) | ||
| 2124 | 'etags)) | ||
| 2119 | 2125 | ||
| 2120 | (cl-defmethod xref-backend-identifier-at-point ((_backend (eql 'etags))) | 2126 | (cl-defmethod xref-backend-identifier-at-point ((_backend (eql 'etags))) |
| 2121 | (find-tag--default)) | 2127 | (find-tag--default)) |
diff --git a/lisp/progmodes/make-mode.el b/lisp/progmodes/make-mode.el index 8856856100e..e34eaba3150 100644 --- a/lisp/progmodes/make-mode.el +++ b/lisp/progmodes/make-mode.el | |||
| @@ -331,7 +331,7 @@ not be enclosed in { } or ( )." | |||
| 331 | &rest fl-keywords) | 331 | &rest fl-keywords) |
| 332 | `(;; Do macro assignments. These get the "variable-name" face. | 332 | `(;; Do macro assignments. These get the "variable-name" face. |
| 333 | (,makefile-macroassign-regex | 333 | (,makefile-macroassign-regex |
| 334 | (1 font-lock-variable-name-face) | 334 | (1 'font-lock-variable-name-face) |
| 335 | ;; This is for after != | 335 | ;; This is for after != |
| 336 | (2 'makefile-shell prepend t) | 336 | (2 'makefile-shell prepend t) |
| 337 | ;; This is for after normal assignment | 337 | ;; This is for after normal assignment |
| @@ -340,10 +340,10 @@ not be enclosed in { } or ( )." | |||
| 340 | ;; Rule actions. | 340 | ;; Rule actions. |
| 341 | ;; FIXME: When this spans multiple lines we need font-lock-multiline. | 341 | ;; FIXME: When this spans multiple lines we need font-lock-multiline. |
| 342 | (makefile-match-action | 342 | (makefile-match-action |
| 343 | (1 font-lock-type-face nil t) | 343 | (1 'font-lock-type-face nil t) |
| 344 | (2 'makefile-shell prepend) | 344 | (2 'makefile-shell prepend) |
| 345 | ;; Only makepp has builtin commands. | 345 | ;; Only makepp has builtin commands. |
| 346 | (3 font-lock-builtin-face prepend t)) | 346 | (3 'font-lock-builtin-face prepend t)) |
| 347 | 347 | ||
| 348 | ;; Variable references even in targets/strings/comments. | 348 | ;; Variable references even in targets/strings/comments. |
| 349 | (,var 2 font-lock-variable-name-face prepend) | 349 | (,var 2 font-lock-variable-name-face prepend) |
| @@ -364,11 +364,11 @@ not be enclosed in { } or ( )." | |||
| 364 | (string-replace "-" "[_-]" (regexp-opt (cdr keywords) t)) | 364 | (string-replace "-" "[_-]" (regexp-opt (cdr keywords) t)) |
| 365 | (regexp-opt keywords t))) | 365 | (regexp-opt keywords t))) |
| 366 | "\\>[ \t]*\\([^: \t\n#]*\\)") | 366 | "\\>[ \t]*\\([^: \t\n#]*\\)") |
| 367 | (1 font-lock-keyword-face) (2 font-lock-variable-name-face)))) | 367 | (1 'font-lock-keyword-face) (2 'font-lock-variable-name-face)))) |
| 368 | 368 | ||
| 369 | ,@(if negation | 369 | ,@(if negation |
| 370 | `((,negation (1 font-lock-negation-char-face prepend) | 370 | `((,negation (1 'font-lock-negation-char-face prepend) |
| 371 | (2 font-lock-negation-char-face prepend t)))) | 371 | (2 'font-lock-negation-char-face prepend t)))) |
| 372 | 372 | ||
| 373 | ,@(if space | 373 | ,@(if space |
| 374 | '(;; Highlight lines that contain just whitespace. | 374 | '(;; Highlight lines that contain just whitespace. |
| @@ -436,9 +436,9 @@ not be enclosed in { } or ( )." | |||
| 436 | 436 | ||
| 437 | ;; Colon modifier keywords. | 437 | ;; Colon modifier keywords. |
| 438 | '("\\(:\\s *\\)\\(build_c\\(?:ache\\|heck\\)\\|env\\(?:ironment\\)?\\|foreach\\|signature\\|scanner\\|quickscan\\|smartscan\\)\\>\\([^:\n]*\\)" | 438 | '("\\(:\\s *\\)\\(build_c\\(?:ache\\|heck\\)\\|env\\(?:ironment\\)?\\|foreach\\|signature\\|scanner\\|quickscan\\|smartscan\\)\\>\\([^:\n]*\\)" |
| 439 | (1 font-lock-type-face t) | 439 | (1 'font-lock-type-face t) |
| 440 | (2 font-lock-keyword-face t) | 440 | (2 'font-lock-keyword-face t) |
| 441 | (3 font-lock-variable-name-face t)) | 441 | (3 'font-lock-variable-name-face t)) |
| 442 | 442 | ||
| 443 | ;; $(function ...) $((function ...)) ${...} ${{...}} $[...] $[[...]] | 443 | ;; $(function ...) $((function ...)) ${...} ${{...}} $[...] $[[...]] |
| 444 | '("[^$]\\$\\(?:((?\\|{{?\\|\\[\\[?\\)\\([-a-zA-Z0-9_.]+\\s \\)" | 444 | '("[^$]\\$\\(?:((?\\|{{?\\|\\[\\[?\\)\\([-a-zA-Z0-9_.]+\\s \\)" |
diff --git a/lisp/progmodes/project.el b/lisp/progmodes/project.el index 997c876b1fa..9e5a8be5e13 100644 --- a/lisp/progmodes/project.el +++ b/lisp/progmodes/project.el | |||
| @@ -84,6 +84,12 @@ | |||
| 84 | ;; This project type can also be used for non-VCS controlled | 84 | ;; This project type can also be used for non-VCS controlled |
| 85 | ;; directories, see the variable `project-vc-extra-root-markers'. | 85 | ;; directories, see the variable `project-vc-extra-root-markers'. |
| 86 | ;; | 86 | ;; |
| 87 | ;; Some of the methods on this backend cache their computations for time | ||
| 88 | ;; determined either by variable `project-vc-cache-timeout' or | ||
| 89 | ;; `project-vc-non-essential-cache-timeout', depending on whether the | ||
| 90 | ;; MAYBE-PROMPT argument to `project-current' is non-nil, or the value | ||
| 91 | ;; of `non-essential' when project methods are called. | ||
| 92 | ;; | ||
| 87 | ;; Utils: | 93 | ;; Utils: |
| 88 | ;; | 94 | ;; |
| 89 | ;; `project-combine-directories' and `project-subtract-directories', | 95 | ;; `project-combine-directories' and `project-subtract-directories', |
| @@ -275,7 +281,8 @@ of the project instance object." | |||
| 275 | (if pr | 281 | (if pr |
| 276 | (project-remember-project pr) | 282 | (project-remember-project pr) |
| 277 | (project--remove-from-project-list | 283 | (project--remove-from-project-list |
| 278 | directory "Project `%s' not found; removed from list") | 284 | (abbreviate-file-name directory) |
| 285 | "Project `%s' not found; removed from list") | ||
| 279 | (setq pr (cons 'transient directory)))) | 286 | (setq pr (cons 'transient directory)))) |
| 280 | pr)) | 287 | pr)) |
| 281 | 288 | ||
| @@ -586,16 +593,74 @@ project backend implementation of `project-external-roots'.") | |||
| 586 | 593 | ||
| 587 | See `project-vc-extra-root-markers' for the marker value format.") | 594 | See `project-vc-extra-root-markers' for the marker value format.") |
| 588 | 595 | ||
| 589 | ;; FIXME: Should perhaps use `vc--repo-*prop' functions | 596 | (defvar project-vc-cache-timeout '((file-remote-p . nil) |
| 590 | ;; (after promoting those to public). --spwhitton | 597 | (always . 2)) |
| 598 | "Number of seconds to cache a value in VC-aware project methods. | ||
| 599 | It can be nil, a number, or an alist where | ||
| 600 | the key is a predicate, and the value is a number. | ||
| 601 | A predicate function should take a directory string and if it returns | ||
| 602 | non-nil, the corresponding value will be used as the timeout. | ||
| 603 | Set to nil to disable time-based expiration.") | ||
| 604 | |||
| 605 | (defvar project-vc-non-essential-cache-timeout '((file-remote-p . nil) | ||
| 606 | (always . 300)) | ||
| 607 | "Number of seconds to cache non-essential information. | ||
| 608 | The format of the value is same as `project-vc-cache-timeout', but while | ||
| 609 | the former is intended for interactive commands, this variable uses | ||
| 610 | higher numbers, intended for \"background\" things like | ||
| 611 | `project-mode-line' indicators and `project-uniquify-dirname-transform'. | ||
| 612 | It is used when `non-essential' is non-nil.") | ||
| 613 | |||
| 614 | (defun project--get-cached (dir key) | ||
| 615 | (let ((cached (vc-file-getprop dir key)) | ||
| 616 | (current-time (float-time))) | ||
| 617 | (when (and (numberp (cdr cached)) | ||
| 618 | ;; Support package upgrade mid-session. | ||
| 619 | (let* ((project-vc-cache-timeout | ||
| 620 | (if non-essential | ||
| 621 | project-vc-non-essential-cache-timeout | ||
| 622 | project-vc-cache-timeout)) | ||
| 623 | (timeout | ||
| 624 | (cond | ||
| 625 | ((numberp project-vc-cache-timeout) | ||
| 626 | project-vc-cache-timeout) | ||
| 627 | ((null project-vc-cache-timeout) | ||
| 628 | nil) | ||
| 629 | ((listp project-vc-cache-timeout) | ||
| 630 | (cdr | ||
| 631 | (seq-find (lambda (pair) | ||
| 632 | (and (functionp (car pair)) | ||
| 633 | (funcall (car pair) dir))) | ||
| 634 | project-vc-cache-timeout))) | ||
| 635 | (t nil)))) | ||
| 636 | (or (null timeout) | ||
| 637 | (< (- current-time (cdr cached)) timeout)))) | ||
| 638 | (car cached)))) | ||
| 639 | |||
| 640 | (defun project--set-cached (dir key value) | ||
| 641 | (vc-file-setprop dir key (cons value (float-time)))) | ||
| 642 | |||
| 643 | ;; TODO: We can have our own, separate obarray. | ||
| 644 | (defun project--clear-cache () | ||
| 645 | (obarray-map | ||
| 646 | (lambda (sym) | ||
| 647 | (if (get sym 'project-vc) | ||
| 648 | (put sym 'project-vc nil))) | ||
| 649 | vc-file-prop-obarray)) | ||
| 650 | |||
| 591 | (defun project-try-vc (dir) | 651 | (defun project-try-vc (dir) |
| 592 | ;; FIXME: Learn to invalidate when the value changes: | 652 | "Returns a project value corresponding to DIR from the VC-aware backend. |
| 593 | ;; `project-vc-merge-submodules' or `project-vc-extra-root-markers'. | 653 | |
| 594 | (or (vc-file-getprop dir 'project-vc) | 654 | The value is cached, and depending on whether MAYBE-PROMPT was non-nil |
| 595 | ;; FIXME: Cache for a shorter time (bug#78545). | 655 | in the `project-current' call, the timeout is determined by |
| 596 | (let ((res (project-try-vc--search dir))) | 656 | `project-vc-cache-timeout' or `project-vc-non-essential-cache-timeout'." |
| 597 | (and res (vc-file-setprop dir 'project-vc res)) | 657 | (let ((cached (project--get-cached dir 'project-vc))) |
| 598 | res))) | 658 | (if (eq cached 'none) |
| 659 | nil | ||
| 660 | (or cached | ||
| 661 | (let ((res (project-try-vc--search dir))) | ||
| 662 | (project--set-cached dir 'project-vc (or res 'none)) | ||
| 663 | res))))) | ||
| 599 | 664 | ||
| 600 | (defun project-try-vc--search (dir) | 665 | (defun project-try-vc--search (dir) |
| 601 | (let* ((backend-markers | 666 | (let* ((backend-markers |
| @@ -896,13 +961,24 @@ DIRS must contain directory names." | |||
| 896 | (cl-set-difference files dirs :test #'file-in-directory-p)) | 961 | (cl-set-difference files dirs :test #'file-in-directory-p)) |
| 897 | 962 | ||
| 898 | (defun project--value-in-dir (var dir) | 963 | (defun project--value-in-dir (var dir) |
| 964 | (alist-get | ||
| 965 | var | ||
| 966 | (let ((cached (project--get-cached dir 'project-vc-dir-locals))) | ||
| 967 | (if (eq cached 'none) | ||
| 968 | nil | ||
| 969 | (or cached | ||
| 970 | (let ((res (project--read-dir-locals dir))) | ||
| 971 | (project--set-cached dir 'project-vc-dir-locals (or res 'none)) | ||
| 972 | res)))) | ||
| 973 | (symbol-value var))) | ||
| 974 | |||
| 975 | (defun project--read-dir-locals (dir) | ||
| 899 | (with-temp-buffer | 976 | (with-temp-buffer |
| 900 | (setq default-directory (file-name-as-directory dir)) | 977 | (setq default-directory (file-name-as-directory dir)) |
| 978 | ;; Don't use `hack-local-variables-apply' to avoid setting modes. | ||
| 901 | (let ((enable-local-variables :all)) | 979 | (let ((enable-local-variables :all)) |
| 902 | (hack-dir-local-variables)) | 980 | (hack-dir-local-variables)) |
| 903 | ;; Don't use `hack-local-variables-apply' to avoid setting modes. | 981 | file-local-variables-alist)) |
| 904 | (alist-get var file-local-variables-alist | ||
| 905 | (symbol-value var)))) | ||
| 906 | 982 | ||
| 907 | (cl-defmethod project-buffers ((project (head vc))) | 983 | (cl-defmethod project-buffers ((project (head vc))) |
| 908 | (let* ((root (expand-file-name (file-name-as-directory (project-root project)))) | 984 | (let* ((root (expand-file-name (file-name-as-directory (project-root project)))) |
| @@ -924,6 +1000,11 @@ DIRS must contain directory names." | |||
| 924 | (nreverse bufs))) | 1000 | (nreverse bufs))) |
| 925 | 1001 | ||
| 926 | (cl-defmethod project-name ((project (head vc))) | 1002 | (cl-defmethod project-name ((project (head vc))) |
| 1003 | "Returns the name of this VC-aware type PROJECT. | ||
| 1004 | |||
| 1005 | The value is cached, and depending on whether `non-essential' is nil, | ||
| 1006 | the timeout is determined by `project-vc-cache-timeout' or | ||
| 1007 | `project-vc-non-essential-cache-timeout'." | ||
| 927 | (or (project--value-in-dir 'project-vc-name (project-root project)) | 1008 | (or (project--value-in-dir 'project-vc-name (project-root project)) |
| 928 | (cl-call-next-method))) | 1009 | (cl-call-next-method))) |
| 929 | 1010 | ||
| @@ -2206,7 +2287,7 @@ result in `project-list-file'. Announce the project's removal | |||
| 2206 | from the list using REPORT-MESSAGE, which is a format string | 2287 | from the list using REPORT-MESSAGE, which is a format string |
| 2207 | passed to `message' as its first argument." | 2288 | passed to `message' as its first argument." |
| 2208 | (project--ensure-read-project-list) | 2289 | (project--ensure-read-project-list) |
| 2209 | (when-let* ((ent (assoc (abbreviate-file-name project-root) project--list))) | 2290 | (when-let* ((ent (assoc project-root project--list))) |
| 2210 | (setq project--list (delq ent project--list)) | 2291 | (setq project--list (delq ent project--list)) |
| 2211 | (message report-message project-root) | 2292 | (message report-message project-root) |
| 2212 | (project--write-project-list))) | 2293 | (project--write-project-list))) |
| @@ -2385,6 +2466,7 @@ projects. | |||
| 2385 | Display a message at the end summarizing what was found. | 2466 | Display a message at the end summarizing what was found. |
| 2386 | Return the number of detected projects." | 2467 | Return the number of detected projects." |
| 2387 | (interactive "DDirectory: \nP") | 2468 | (interactive "DDirectory: \nP") |
| 2469 | (project--clear-cache) | ||
| 2388 | (project--ensure-read-project-list) | 2470 | (project--ensure-read-project-list) |
| 2389 | (let ((dirs (if recursive | 2471 | (let ((dirs (if recursive |
| 2390 | (directory-files-recursively dir "" t) | 2472 | (directory-files-recursively dir "" t) |
| @@ -2417,12 +2499,18 @@ PREDICATE can be a function with 1 argument which determines which | |||
| 2417 | projects should be deleted." | 2499 | projects should be deleted." |
| 2418 | (dolist (proj (project-known-project-roots)) | 2500 | (dolist (proj (project-known-project-roots)) |
| 2419 | (when (and (funcall (or predicate #'identity) proj) | 2501 | (when (and (funcall (or predicate #'identity) proj) |
| 2420 | (not (file-exists-p proj))) | 2502 | (condition-case-unless-debug nil |
| 2503 | (not (file-exists-p proj)) | ||
| 2504 | (file-error | ||
| 2505 | (yes-or-no-p | ||
| 2506 | (format "Forget unreachable project `%s'? " | ||
| 2507 | proj))))) | ||
| 2421 | (project-forget-project proj)))) | 2508 | (project-forget-project proj)))) |
| 2422 | 2509 | ||
| 2423 | (defun project-forget-zombie-projects (&optional interactive) | 2510 | (defun project-forget-zombie-projects (&optional interactive) |
| 2424 | "Forget all known projects that don't exist any more." | 2511 | "Forget all known projects that don't exist any more." |
| 2425 | (interactive (list t)) | 2512 | (interactive (list t)) |
| 2513 | (project--clear-cache) | ||
| 2426 | (let ((pred (when interactive (alist-get 'interactively project-prune-zombie-projects)))) | 2514 | (let ((pred (when interactive (alist-get 'interactively project-prune-zombie-projects)))) |
| 2427 | (project--delete-zombie-projects pred))) | 2515 | (project--delete-zombie-projects pred))) |
| 2428 | 2516 | ||
| @@ -2435,6 +2523,7 @@ to remove those projects from the index. | |||
| 2435 | Display a message at the end summarizing what was forgotten. | 2523 | Display a message at the end summarizing what was forgotten. |
| 2436 | Return the number of forgotten projects." | 2524 | Return the number of forgotten projects." |
| 2437 | (interactive "DDirectory: \nP") | 2525 | (interactive "DDirectory: \nP") |
| 2526 | (project--clear-cache) | ||
| 2438 | (let ((count 0)) | 2527 | (let ((count 0)) |
| 2439 | (if recursive | 2528 | (if recursive |
| 2440 | (dolist (proj (project-known-project-roots)) | 2529 | (dolist (proj (project-known-project-roots)) |
| @@ -2624,7 +2713,8 @@ slash-separated components from `project-name' will be appended to | |||
| 2624 | the buffer's directory name when buffers from two different projects | 2713 | the buffer's directory name when buffers from two different projects |
| 2625 | would otherwise have the same name." | 2714 | would otherwise have the same name." |
| 2626 | (if-let* ((proj (project-current nil dirname))) | 2715 | (if-let* ((proj (project-current nil dirname))) |
| 2627 | (let ((root (project-root proj))) | 2716 | (let ((root (project-root proj)) |
| 2717 | (non-essential t)) | ||
| 2628 | (expand-file-name | 2718 | (expand-file-name |
| 2629 | (file-name-concat | 2719 | (file-name-concat |
| 2630 | (file-name-directory root) | 2720 | (file-name-directory root) |
| @@ -2634,27 +2724,6 @@ would otherwise have the same name." | |||
| 2634 | 2724 | ||
| 2635 | ;;; Project mode-line | 2725 | ;;; Project mode-line |
| 2636 | 2726 | ||
| 2637 | (defvar project-name-cache-timeout 300 | ||
| 2638 | "Number of seconds to cache the project name. | ||
| 2639 | Used by `project-name-cached'.") | ||
| 2640 | |||
| 2641 | (defun project-name-cached (dir) | ||
| 2642 | "Return the cached project name for the directory DIR. | ||
| 2643 | Until it's cached, retrieve the project name using `project-current' | ||
| 2644 | and `project-name', then put the name to the cache for the time defined | ||
| 2645 | by the variable `project-name-cache-timeout'. This function is useful | ||
| 2646 | for project indicators such as on the mode line." | ||
| 2647 | (let ((cached (vc-file-getprop dir 'project-name)) | ||
| 2648 | (current-time (float-time))) | ||
| 2649 | (if (and cached (< (- current-time (cdr cached)) | ||
| 2650 | project-name-cache-timeout)) | ||
| 2651 | (let ((value (car cached))) | ||
| 2652 | (if (eq value 'none) nil value)) | ||
| 2653 | (let ((res (when-let* ((project (project-current nil dir))) | ||
| 2654 | (project-name project)))) | ||
| 2655 | (vc-file-setprop dir 'project-name (cons (or res 'none) current-time)) | ||
| 2656 | res)))) | ||
| 2657 | |||
| 2658 | ;;;###autoload | 2727 | ;;;###autoload |
| 2659 | (defcustom project-mode-line nil | 2728 | (defcustom project-mode-line nil |
| 2660 | "Whether to show current project name and Project menu on the mode line. | 2729 | "Whether to show current project name and Project menu on the mode line. |
| @@ -2691,7 +2760,9 @@ value is `non-remote', show the project name only for local files." | |||
| 2691 | ;; 'last-coding-system-used' when reading the project name | 2760 | ;; 'last-coding-system-used' when reading the project name |
| 2692 | ;; from .dir-locals.el also enables flyspell-mode (bug#66825). | 2761 | ;; from .dir-locals.el also enables flyspell-mode (bug#66825). |
| 2693 | (when-let* ((last-coding-system-used last-coding-system-used) | 2762 | (when-let* ((last-coding-system-used last-coding-system-used) |
| 2694 | (project-name (project-name-cached default-directory))) | 2763 | (non-essential t) |
| 2764 | (project (project-current)) | ||
| 2765 | (project-name (project-name project))) | ||
| 2695 | (concat | 2766 | (concat |
| 2696 | " " | 2767 | " " |
| 2697 | (propertize | 2768 | (propertize |
diff --git a/lisp/progmodes/python.el b/lisp/progmodes/python.el index b6981c9156c..2a3035c95c5 100644 --- a/lisp/progmodes/python.el +++ b/lisp/progmodes/python.el | |||
| @@ -3366,6 +3366,16 @@ from `python-shell-prompt-regexp', | |||
| 3366 | python-shell--prompt-calculated-output-regexp | 3366 | python-shell--prompt-calculated-output-regexp |
| 3367 | (funcall build-regexp output-prompts))))) | 3367 | (funcall build-regexp output-prompts))))) |
| 3368 | 3368 | ||
| 3369 | (defun python-shell-get-project-name () | ||
| 3370 | "Return the project name for the current buffer. | ||
| 3371 | Use `project-name-cached' if available." | ||
| 3372 | (when (featurep 'project) | ||
| 3373 | (if (fboundp 'project-name-cached) | ||
| 3374 | (project-name-cached default-directory) | ||
| 3375 | (when-let* ((proj (project-current))) | ||
| 3376 | (file-name-nondirectory | ||
| 3377 | (directory-file-name (project-root proj))))))) | ||
| 3378 | |||
| 3369 | (defun python-shell-get-process-name (dedicated) | 3379 | (defun python-shell-get-process-name (dedicated) |
| 3370 | "Calculate the appropriate process name for inferior Python process. | 3380 | "Calculate the appropriate process name for inferior Python process. |
| 3371 | If DEDICATED is nil, this is simply `python-shell-buffer-name'. | 3381 | If DEDICATED is nil, this is simply `python-shell-buffer-name'. |
| @@ -3374,11 +3384,8 @@ name respectively the current project name." | |||
| 3374 | (pcase dedicated | 3384 | (pcase dedicated |
| 3375 | ('nil python-shell-buffer-name) | 3385 | ('nil python-shell-buffer-name) |
| 3376 | ('project | 3386 | ('project |
| 3377 | (if-let* ((proj (and (featurep 'project) | 3387 | (if-let* ((proj-name (python-shell-get-project-name))) |
| 3378 | (project-current)))) | 3388 | (format "%s[%s]" python-shell-buffer-name proj-name) |
| 3379 | (format "%s[%s]" python-shell-buffer-name (file-name-nondirectory | ||
| 3380 | (directory-file-name | ||
| 3381 | (project-root proj)))) | ||
| 3382 | python-shell-buffer-name)) | 3389 | python-shell-buffer-name)) |
| 3383 | (_ (format "%s[%s]" python-shell-buffer-name (buffer-name))))) | 3390 | (_ (format "%s[%s]" python-shell-buffer-name (buffer-name))))) |
| 3384 | 3391 | ||
| @@ -3816,16 +3823,6 @@ variable. | |||
| 3816 | (compilation-shell-minor-mode 1) | 3823 | (compilation-shell-minor-mode 1) |
| 3817 | (python-pdbtrack-setup-tracking)) | 3824 | (python-pdbtrack-setup-tracking)) |
| 3818 | 3825 | ||
| 3819 | (defvar-local python-shell--process-cache) | ||
| 3820 | (defvar-local python-shell--process-cache-valid) | ||
| 3821 | |||
| 3822 | (defun python-shell--invalidate-process-cache () | ||
| 3823 | "Invalidate process cache." | ||
| 3824 | (dolist (buffer (buffer-list)) | ||
| 3825 | (with-current-buffer buffer | ||
| 3826 | (setq python-shell--process-cache nil | ||
| 3827 | python-shell--process-cache-valid nil)))) | ||
| 3828 | |||
| 3829 | (defun python-shell-make-comint (cmd proc-name &optional show internal) | 3826 | (defun python-shell-make-comint (cmd proc-name &optional show internal) |
| 3830 | "Create a Python shell comint buffer. | 3827 | "Create a Python shell comint buffer. |
| 3831 | CMD is the Python command to be executed and PROC-NAME is the | 3828 | CMD is the Python command to be executed and PROC-NAME is the |
| @@ -3842,7 +3839,6 @@ killed." | |||
| 3842 | (let* ((proc-buffer-name | 3839 | (let* ((proc-buffer-name |
| 3843 | (format (if (not internal) "*%s*" " *%s*") proc-name))) | 3840 | (format (if (not internal) "*%s*" " *%s*") proc-name))) |
| 3844 | (when (not (comint-check-proc proc-buffer-name)) | 3841 | (when (not (comint-check-proc proc-buffer-name)) |
| 3845 | (python-shell--invalidate-process-cache) | ||
| 3846 | (let* ((cmdlist (split-string-and-unquote cmd)) | 3842 | (let* ((cmdlist (split-string-and-unquote cmd)) |
| 3847 | (interpreter (car cmdlist)) | 3843 | (interpreter (car cmdlist)) |
| 3848 | (args (cdr cmdlist)) | 3844 | (args (cdr cmdlist)) |
| @@ -3966,15 +3962,7 @@ If current buffer is in `inferior-python-mode', return it." | |||
| 3966 | 3962 | ||
| 3967 | (defun python-shell-get-process () | 3963 | (defun python-shell-get-process () |
| 3968 | "Return inferior Python process for current buffer." | 3964 | "Return inferior Python process for current buffer." |
| 3969 | (unless (and python-shell--process-cache-valid | 3965 | (get-buffer-process (python-shell-get-buffer))) |
| 3970 | (or (not python-shell--process-cache) | ||
| 3971 | (and (process-live-p python-shell--process-cache) | ||
| 3972 | (buffer-live-p | ||
| 3973 | (process-buffer python-shell--process-cache))))) | ||
| 3974 | (setq python-shell--process-cache | ||
| 3975 | (get-buffer-process (python-shell-get-buffer)) | ||
| 3976 | python-shell--process-cache-valid t)) | ||
| 3977 | python-shell--process-cache) | ||
| 3978 | 3966 | ||
| 3979 | (defun python-shell-get-process-or-error (&optional interactivep) | 3967 | (defun python-shell-get-process-or-error (&optional interactivep) |
| 3980 | "Return inferior Python process for current buffer or signal error. | 3968 | "Return inferior Python process for current buffer or signal error. |
| @@ -5854,7 +5842,7 @@ Set to nil by `python-eldoc-function' if | |||
| 5854 | 5842 | ||
| 5855 | (defcustom python-eldoc-function-timeout 1 | 5843 | (defcustom python-eldoc-function-timeout 1 |
| 5856 | "Timeout for `python-eldoc-function' in seconds." | 5844 | "Timeout for `python-eldoc-function' in seconds." |
| 5857 | :type 'integer | 5845 | :type 'number |
| 5858 | :version "25.1") | 5846 | :version "25.1") |
| 5859 | 5847 | ||
| 5860 | (defcustom python-eldoc-function-timeout-permanent t | 5848 | (defcustom python-eldoc-function-timeout-permanent t |
diff --git a/lisp/progmodes/xref.el b/lisp/progmodes/xref.el index 84a3fa4dfba..1e51b23eaff 100644 --- a/lisp/progmodes/xref.el +++ b/lisp/progmodes/xref.el | |||
| @@ -247,11 +247,9 @@ generic functions.") | |||
| 247 | 247 | ||
| 248 | ;;;###autoload | 248 | ;;;###autoload |
| 249 | (defun xref-find-backend () | 249 | (defun xref-find-backend () |
| 250 | (or | 250 | (run-hook-with-args-until-success 'xref-backend-functions)) |
| 251 | (run-hook-with-args-until-success 'xref-backend-functions) | ||
| 252 | (user-error "No Xref backend available"))) | ||
| 253 | 251 | ||
| 254 | (cl-defgeneric xref-backend-definitions (backend identifier) | 252 | (cl-defgeneric xref-backend-definitions (_backend _identifier) |
| 255 | "Find definitions of IDENTIFIER. | 253 | "Find definitions of IDENTIFIER. |
| 256 | 254 | ||
| 257 | The result must be a list of xref objects. If IDENTIFIER | 255 | The result must be a list of xref objects. If IDENTIFIER |
| @@ -264,7 +262,8 @@ IDENTIFIER can be any string returned by | |||
| 264 | `xref-backend-identifier-at-point', or from the table returned by | 262 | `xref-backend-identifier-at-point', or from the table returned by |
| 265 | `xref-backend-identifier-completion-table'. | 263 | `xref-backend-identifier-completion-table'. |
| 266 | 264 | ||
| 267 | To create an xref object, call `xref-make'.") | 265 | To create an xref object, call `xref-make'." |
| 266 | (xref--no-backend-available)) | ||
| 268 | 267 | ||
| 269 | (cl-defgeneric xref-backend-references (_backend identifier) | 268 | (cl-defgeneric xref-backend-references (_backend identifier) |
| 270 | "Find references of IDENTIFIER. | 269 | "Find references of IDENTIFIER. |
| @@ -285,12 +284,13 @@ The default implementation uses `xref-references-in-directory'." | |||
| 285 | (xref--project-root pr) | 284 | (xref--project-root pr) |
| 286 | (project-external-roots pr)))))) | 285 | (project-external-roots pr)))))) |
| 287 | 286 | ||
| 288 | (cl-defgeneric xref-backend-apropos (backend pattern) | 287 | (cl-defgeneric xref-backend-apropos (_backend _pattern) |
| 289 | "Find all symbols that match PATTERN string. | 288 | "Find all symbols that match PATTERN string. |
| 290 | The second argument has the same meaning as in `apropos'. | 289 | The second argument has the same meaning as in `apropos'. |
| 291 | 290 | ||
| 292 | If BACKEND is implemented in Lisp, it can use | 291 | If BACKEND is implemented in Lisp, it can use |
| 293 | `xref-apropos-regexp' to convert the pattern to regexp.") | 292 | `xref-apropos-regexp' to convert the pattern to regexp." |
| 293 | (xref--no-backend-available)) | ||
| 294 | 294 | ||
| 295 | (cl-defgeneric xref-backend-identifier-at-point (_backend) | 295 | (cl-defgeneric xref-backend-identifier-at-point (_backend) |
| 296 | "Return the relevant identifier at point. | 296 | "Return the relevant identifier at point. |
| @@ -306,8 +306,9 @@ recognize and then delegate the work to an external process." | |||
| 306 | (let ((thing (thing-at-point 'symbol))) | 306 | (let ((thing (thing-at-point 'symbol))) |
| 307 | (and thing (substring-no-properties thing)))) | 307 | (and thing (substring-no-properties thing)))) |
| 308 | 308 | ||
| 309 | (cl-defgeneric xref-backend-identifier-completion-table (backend) | 309 | (cl-defgeneric xref-backend-identifier-completion-table (_backend) |
| 310 | "Return the completion table for identifiers.") | 310 | "Return the completion table for identifiers." |
| 311 | nil) | ||
| 311 | 312 | ||
| 312 | (cl-defgeneric xref-backend-identifier-completion-ignore-case (_backend) | 313 | (cl-defgeneric xref-backend-identifier-completion-ignore-case (_backend) |
| 313 | "Return t if case is not significant in identifier completion." | 314 | "Return t if case is not significant in identifier completion." |
| @@ -329,6 +330,10 @@ KEY extracts the key from an element." | |||
| 329 | (cl-loop for key being hash-keys of table using (hash-values value) | 330 | (cl-loop for key being hash-keys of table using (hash-values value) |
| 330 | collect (cons key (nreverse value))))) | 331 | collect (cons key (nreverse value))))) |
| 331 | 332 | ||
| 333 | (defun xref--no-backend-available () | ||
| 334 | (user-error | ||
| 335 | "No Xref backend. Try `M-x eglot', `M-x visit-tags-table', or `M-x etags-regen-mode'.")) | ||
| 336 | |||
| 332 | (defun xref--insert-propertized (props &rest strings) | 337 | (defun xref--insert-propertized (props &rest strings) |
| 333 | "Insert STRINGS with text properties PROPS." | 338 | "Insert STRINGS with text properties PROPS." |
| 334 | (let ((start (point))) | 339 | (let ((start (point))) |