From 2a62273f3bfd0240c797e9c294e328e669a24170 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20T=C3=A1vora?= Date: Fri, 7 Apr 2023 14:55:01 +0100 Subject: [PATCH 1/5] Backport: Eglot: no more tests based on Pylsp (bug#62694) The functionality under test in eglot.el is exactly the same, but use the clangd server only, as that is used in more tests, and it is much easier to check if it misbehaves or not. Tests pass with clangd version 15. * test/lisp/progmodes/eglot-tests.el (python): Don't require it. (eglot--call-with-fixture): Simplify. (eglot--wait-for-clangd): New helper. (eglot-test-basic-completions) (eglot-test-non-unique-completions, eglot-test-basic-xref) (eglot-test-snippet-completions) (eglot-test-snippet-completions-with-company) (eglot-test-eldoc-after-completions, eglot-test-multiline-eldoc): Use clangd, not pylsp. (eglot-test-formatting): Renamed from eglot-test-python-autopep-formatting. (eglot-test-python-yapf-formatting): Remove. (cherry picked from commit fc8230f3362b12955152f48565a6c670b4c4cc88) --- test/lisp/progmodes/eglot-tests.el | 197 +++++++++++++---------------- 1 file changed, 85 insertions(+), 112 deletions(-) diff --git a/test/lisp/progmodes/eglot-tests.el b/test/lisp/progmodes/eglot-tests.el index 2388cf9ef51..7489ad53645 100644 --- a/test/lisp/progmodes/eglot-tests.el +++ b/test/lisp/progmodes/eglot-tests.el @@ -50,7 +50,6 @@ (require 'tramp) ; must be prior ert-x (require 'ert-x) ; ert-simulate-command (require 'edebug) -(require 'python) ; some tests use pylsp (require 'cc-mode) ; c-mode-hook (require 'company nil t) (require 'yasnippet nil t) @@ -119,8 +118,6 @@ then restored." ,(format "HOME=%s" (expand-file-name (format "~%s" (user-login-name))))) process-environment)) - ;; Prevent "Can't guess python-indent-offset ..." messages. - (python-indent-guess-indent-offset-verbose . nil) (eglot-server-initialized-hook (lambda (server) (push server new-servers)))) (setq created-files (mapcan #'eglot--make-file-or-dir file-specs)) @@ -533,90 +530,101 @@ Pass TIMEOUT to `eglot--with-timeout'." (should (equal (buffer-string) "int bar() {return 42;} int main() {return bar();}"))))) +(defun eglot--wait-for-clangd () + (eglot--sniffing (:server-notifications s-notifs) + (should (eglot--tests-connect)) + (eglot--wait-for (s-notifs 20) (&key method &allow-other-keys) + (string= method "textDocument/publishDiagnostics")))) + (ert-deftest eglot-test-basic-completions () - "Test basic autocompletion in a python LSP." - (skip-unless (executable-find "pylsp")) + "Test basic autocompletion in a clangd LSP." + (skip-unless (executable-find "clangd")) (eglot--with-fixture - `(("project" . (("something.py" . "import sys\nsys.exi")))) + `(("project" . (("coiso.c" . "#include \nint main () {fprin")))) (with-current-buffer - (eglot--find-file-noselect "project/something.py") - (should (eglot--tests-connect)) + (eglot--find-file-noselect "project/coiso.c") + (eglot--sniffing (:server-notifications s-notifs) + (eglot--wait-for-clangd) + (eglot--wait-for (s-notifs 20) (&key method &allow-other-keys) + (string= method "textDocument/publishDiagnostics"))) (goto-char (point-max)) (completion-at-point) - (should (looking-back "sys.exit"))))) + (message (buffer-string)) + (should (looking-back "fprintf.?"))))) (ert-deftest eglot-test-non-unique-completions () "Test completion resulting in 'Complete, but not unique'." - (skip-unless (executable-find "pylsp")) + (skip-unless (executable-find "clangd")) (eglot--with-fixture - '(("project" . (("something.py" . "foo=1\nfoobar=2\nfoo")))) + `(("project" . (("coiso.c" . + ,(concat "int foo; int fooey;" + "int main() {foo"))))) (with-current-buffer - (eglot--find-file-noselect "project/something.py") - (should (eglot--tests-connect)) + (eglot--find-file-noselect "project/coiso.c") + (eglot--wait-for-clangd) (goto-char (point-max)) - (completion-at-point)) - ;; FIXME: `current-message' doesn't work here :-( + (completion-at-point) + ;; FIXME: `current-message' doesn't work here :-( (with-current-buffer (messages-buffer) (save-excursion (goto-char (point-max)) (forward-line -1) - (should (looking-at "Complete, but not unique")))))) + (should (looking-at "Complete, but not unique"))))))) (ert-deftest eglot-test-basic-xref () - "Test basic xref functionality in a python LSP." - (skip-unless (executable-find "pylsp")) + "Test basic xref functionality in a clangd LSP." + (skip-unless (executable-find "clangd")) (eglot--with-fixture - `(("project" . (("something.py" . "def foo(): pass\ndef bar(): foo()")))) + `(("project" . (("coiso.c" . + ,(concat "int foo=42; int fooey;" + "int main() {foo=82;}"))))) (with-current-buffer - (eglot--find-file-noselect "project/something.py") + (eglot--find-file-noselect "project/coiso.c") (should (eglot--tests-connect)) - (search-forward "bar(): f") + (search-forward "{foo") (call-interactively 'xref-find-definitions) - (should (looking-at "foo(): pass"))))) + (should (looking-at "foo=42"))))) -(defvar eglot--test-python-buffer +(defvar eglot--test-c-buffer "\ -def foobarquux(a, b, c=True): pass -def foobazquuz(d, e, f): pass +void foobarquux(int a, int b, int c){}; +void foobazquuz(int a, int b, int f){}; +int main() { ") (declare-function yas-minor-mode nil) (ert-deftest eglot-test-snippet-completions () - "Test simple snippet completion in a python LSP." - (skip-unless (and (executable-find "pylsp") + "Test simple snippet completion in a clangd LSP." + (skip-unless (and (executable-find "clangd") (functionp 'yas-minor-mode))) (eglot--with-fixture - `(("project" . (("something.py" . ,eglot--test-python-buffer)))) + `(("project" . (("coiso.c" . ,eglot--test-c-buffer)))) (with-current-buffer - (eglot--find-file-noselect "project/something.py") + (eglot--find-file-noselect "project/coiso.c") (yas-minor-mode 1) - (let ((eglot-workspace-configuration - `((:pylsp . (:plugins (:jedi_completion (:include_params t))))))) - (should (eglot--tests-connect))) + (eglot--wait-for-clangd) (goto-char (point-max)) (insert "foobar") (completion-at-point) (should (looking-back "foobarquux(")) - (should (looking-at "a, b)"))))) + (should (looking-at "int a, int b, int c)"))))) (defvar company-candidates) (declare-function company-mode nil) (declare-function company-complete nil) (ert-deftest eglot-test-snippet-completions-with-company () - "Test simple snippet completion in a python LSP." - (skip-unless (and (executable-find "pylsp") + "Test simple snippet completion in a clangd LSP." + (skip-unless (and (executable-find "clangd") (functionp 'yas-minor-mode) (functionp 'company-complete))) (eglot--with-fixture - `(("project" . (("something.py" . ,eglot--test-python-buffer)))) + `(("project" . (("coiso.c" . ,eglot--test-c-buffer)))) (with-current-buffer - (eglot--find-file-noselect "project/something.py") + (eglot--find-file-noselect "project/coiso.c") (yas-minor-mode 1) - (let ((eglot-workspace-configuration - `((:pylsp . (:plugins (:jedi_completion (:include_params t))))))) - (should (eglot--tests-connect))) + (eglot--wait-for-clangd) (goto-char (point-max)) (insert "foo") (company-mode) @@ -624,98 +632,63 @@ def foobazquuz(d, e, f): pass (should (looking-back "fooba")) (should (= 2 (length company-candidates))) ;; this last one is brittle, since there it is possible that - ;; pylsp will change the representation of this candidate - (should (member "foobazquuz(d, e, f)" company-candidates))))) + ;; clangd will change the representation of this candidate + (should (member "foobazquuz(int a, int b, int f)" company-candidates))))) (ert-deftest eglot-test-eldoc-after-completions () - "Test documentation echo in a python LSP." - (skip-unless (executable-find "pylsp")) + "Test documentation echo in a clangd LSP." + (skip-unless (executable-find "clangd")) (eglot--with-fixture - `(("project" . (("something.py" . "import sys\nsys.exi")))) + `(("project" . (("coiso.c" . "#include \nint main () {fprin")))) (with-current-buffer - (eglot--find-file-noselect "project/something.py") - (should (eglot--tests-connect)) + (eglot--find-file-noselect "project/coiso.c") + (eglot--wait-for-clangd) (goto-char (point-max)) (completion-at-point) - (should (looking-back "sys.exit")) - (should (string-match "^exit" (eglot--tests-force-full-eldoc)))))) + (message (buffer-string)) + (should (looking-back "fprintf(?")) + (unless (= (char-before) ?\()) (insert "()") (backward-char) + (eglot--signal-textDocument/didChange) + (should (string-match "^fprintf" (eglot--tests-force-full-eldoc)))))) (ert-deftest eglot-test-multiline-eldoc () - "Test if suitable amount of lines of hover info are shown." - (skip-unless (executable-find "pylsp")) + "Test Eldoc documentation from multiple osurces." + (skip-unless (executable-find "clangd")) (eglot--with-fixture - `(("project" . (("hover-first.py" . "from datetime import datetime")))) + `(("project" . (("coiso.c" . + "#include \nint main () {fprintf(blergh);}")))) (with-current-buffer - (eglot--find-file-noselect "project/hover-first.py") - (should (eglot--tests-connect)) - (goto-char (point-max)) - ;; one-line - (let* ((eldoc-echo-area-use-multiline-p t) - (captured-message (eglot--tests-force-full-eldoc))) - (should (string-match "datetim" captured-message)) + (eglot--find-file-noselect "project/coiso.c") + (search-forward "fprintf(ble") + (eglot--wait-for-clangd) + (flymake-start nil t) ;; thing brings in the "unknown identifier blergh" + (let* ((captured-message (eglot--tests-force-full-eldoc))) + ;; check for signature and error message in the result + (should (string-match "fprintf" captured-message)) + (should (string-match "blergh" captured-message)) (should (cl-find ?\n captured-message)))))) -(ert-deftest eglot-test-single-line-eldoc () - "Test if suitable amount of lines of hover info are shown." - (skip-unless (executable-find "pylsp")) - (eglot--with-fixture - `(("project" . (("hover-first.py" . "from datetime import datetime")))) - (with-current-buffer - (eglot--find-file-noselect "project/hover-first.py") - (should (eglot--tests-connect)) - (goto-char (point-max)) - ;; one-line - (let* ((eldoc-echo-area-use-multiline-p nil) - (captured-message (eglot--tests-force-full-eldoc))) - (should (string-match "datetim" captured-message)) - (should (not (cl-find ?\n eldoc-last-message))))))) - -(ert-deftest eglot-test-python-autopep-formatting () - "Test formatting in the pylsp python LSP. -pylsp prefers autopep over yafp, despite its README stating the contrary." +(ert-deftest eglot-test-formatting () + "Test formatting in the clangd server." ;; Beware, default autopep rules can change over time, which may ;; affect this test. - (skip-unless (and (executable-find "pylsp") - (executable-find "autopep8"))) + (skip-unless (executable-find "clangd")) (eglot--with-fixture - `(("project" . (("something.py" . "def a():pass\n\ndef b():pass")))) + `(("project" . (("coiso.c" . ,(concat "#include \n" + "int main(){fprintf(blergh);}" + "int ble{\n\nreturn 0;}"))))) (with-current-buffer - (eglot--find-file-noselect "project/something.py") - (should (eglot--tests-connect)) + (eglot--find-file-noselect "project/coiso.c") + (eglot--wait-for-clangd) + (forward-line) ;; Try to format just the second line - (search-forward "b():pa") (eglot-format (line-beginning-position) (line-end-position)) - (should (looking-at "ss")) - (should - (or (string= (buffer-string) "def a():pass\n\n\ndef b(): pass\n") - ;; autopep8 2.0.0 (pycodestyle: 2.9.1) - (string= (buffer-string) "def a():pass\n\ndef b(): pass"))) - ;; now format the whole buffer + (should (looking-at "int main() { fprintf(blergh); }")) + ;; ;; now format the whole buffer (eglot-format-buffer) (should - (string= (buffer-string) "def a(): pass\n\n\ndef b(): pass\n"))))) - -(ert-deftest eglot-test-python-yapf-formatting () - "Test formatting in the pylsp python LSP." - (skip-unless (and (executable-find "pylsp") - (not (executable-find "autopep8")) - (or (executable-find "yapf") - (executable-find "yapf3")))) - (eglot--with-fixture - `(("project" . (("something.py" . "def a():pass\ndef b():pass")))) - (with-current-buffer - (eglot--find-file-noselect "project/something.py") - (should (eglot--tests-connect)) - ;; Try to format just the second line - (search-forward "b():pa") - (eglot-format (line-beginning-position) (line-end-position)) - (should (looking-at "ss")) - (should - (string= (buffer-string) "def a():pass\n\n\ndef b():\n pass\n")) - ;; now format the whole buffer - (eglot-format-buffer) - (should - (string= (buffer-string) "def a():\n pass\n\n\ndef b():\n pass\n"))))) + (string= (buffer-string) + "#include \nint main() { fprintf(blergh); }\nint ble { return 0; }"))))) (ert-deftest eglot-test-rust-on-type-formatting () "Test textDocument/onTypeFormatting against rust-analyzer." From 57490fff6ecf3cc1de7d8d833d1b6c895330b762 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20T=C3=A1vora?= Date: Fri, 7 Apr 2023 18:52:06 +0100 Subject: [PATCH 2/5] ; Backport: Eglot: fix misplaced parenthesis in last commit to eglot-tests.el * test/lisp/progmodes/eglot-tests.el (eglot-test-eldoc-after-completions): Fix misplaced parenthesis. (cherry picked from commit 3aedd5c920560fe6456a860a900be40a35e850d2) --- test/lisp/progmodes/eglot-tests.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/lisp/progmodes/eglot-tests.el b/test/lisp/progmodes/eglot-tests.el index 7489ad53645..984c1bf9916 100644 --- a/test/lisp/progmodes/eglot-tests.el +++ b/test/lisp/progmodes/eglot-tests.el @@ -647,7 +647,7 @@ int main() { (completion-at-point) (message (buffer-string)) (should (looking-back "fprintf(?")) - (unless (= (char-before) ?\()) (insert "()") (backward-char) + (unless (= (char-before) ?\() (insert "()") (backward-char)) (eglot--signal-textDocument/didChange) (should (string-match "^fprintf" (eglot--tests-force-full-eldoc)))))) From d6af1f14982db408c92f5c879b28e8be71e85054 Mon Sep 17 00:00:00 2001 From: "Basil L. Contovounesios" Date: Sun, 9 Apr 2023 19:08:06 +0100 Subject: [PATCH 3/5] ; doc/lispref/windows.texi: Fix @pxref paren. --- doc/lispref/windows.texi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/lispref/windows.texi b/doc/lispref/windows.texi index 01ac6fb901a..0196ed0e813 100644 --- a/doc/lispref/windows.texi +++ b/doc/lispref/windows.texi @@ -3216,7 +3216,7 @@ any window it creates as dedicated to its buffer (@pxref{Dedicated Windows}). It does that by calling @code{set-window-dedicated-p} with the chosen window as first argument and the entry's value as second. Side windows are by default dedicated with the value @code{side} -((@pxref{Side Window Options and Functions}). +(@pxref{Side Window Options and Functions}). @vindex preserve-size@r{, a buffer display action alist entry} @item preserve-size From 589959fb09d8a8f60179e1cceca4c3777b8c7719 Mon Sep 17 00:00:00 2001 From: Dmitry Gutov Date: Mon, 10 Apr 2023 02:08:48 +0300 Subject: [PATCH 4/5] project-search: Pipe the list of files through 'file-regular-p' * lisp/progmodes/project.el (project-search): Pipe the list of files through 'file-regular-p' to skip directories (bug#62735). --- lisp/progmodes/project.el | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lisp/progmodes/project.el b/lisp/progmodes/project.el index 11228226592..a18b918db62 100644 --- a/lisp/progmodes/project.el +++ b/lisp/progmodes/project.el @@ -1216,7 +1216,10 @@ To continue searching for the next match, use the command \\[fileloop-continue]." (interactive "sSearch (regexp): ") (fileloop-initialize-search - regexp (project-files (project-current t)) 'default) + regexp + ;; XXX: See the comment in project-query-replace-regexp. + (cl-delete-if-not #'file-regular-p (project-files (project-current t))) + 'default) (fileloop-continue)) ;;;###autoload From db8f207e52fc969e0dcf30e197bcfaa4fa1d2b6e Mon Sep 17 00:00:00 2001 From: Dmitry Gutov Date: Mon, 10 Apr 2023 02:58:46 +0300 Subject: [PATCH 5/5] Fix some cases of incomplete code's indentation [c/c++-ts-mode] * lisp/progmodes/c-ts-mode.el (c-ts-base--before-indent): Try to guess when the parse tree is incomplete, and provide a better node to indent against (bug#62717). (c-ts-base-mode): Set up advice for local treesit-indent-function. --- lisp/progmodes/c-ts-mode.el | 17 +++++++++++++++++ .../progmodes/c-ts-mode-resources/indent.erts | 14 ++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/lisp/progmodes/c-ts-mode.el b/lisp/progmodes/c-ts-mode.el index 981c7766375..83e89c3a335 100644 --- a/lisp/progmodes/c-ts-mode.el +++ b/lisp/progmodes/c-ts-mode.el @@ -859,6 +859,18 @@ the semicolon. This function skips the semicolon." (goto-char (match-end 0))) (treesit-default-defun-skipper)) +(defun c-ts-base--before-indent (args) + (pcase-let ((`(,node ,parent ,bol) args)) + (when (null node) + (let ((smallest-node (treesit-node-at (point)))) + ;; "Virtual" closer curly added by the + ;; parser's error recovery. + (when (and (equal (treesit-node-type smallest-node) "}") + (equal (treesit-node-end smallest-node) + (treesit-node-start smallest-node))) + (setq parent (treesit-node-parent smallest-node))))) + (list node parent bol))) + (defun c-ts-mode-indent-defun () "Indent the current top-level declaration syntactically. @@ -904,6 +916,11 @@ the semicolon. This function skips the semicolon." ;; function_definitions, so we need to find the top-level node. (setq-local treesit-defun-prefer-top-level t) + ;; When the code is in incomplete state, try to make a better guess + ;; about which node to indent against. + (add-function :filter-args (local 'treesit-indent-function) + #'c-ts-base--before-indent) + ;; Indent. (when (eq c-ts-mode-indent-style 'linux) (setq-local indent-tabs-mode t)) diff --git a/test/lisp/progmodes/c-ts-mode-resources/indent.erts b/test/lisp/progmodes/c-ts-mode-resources/indent.erts index 5cdefe2122c..221b3d809af 100644 --- a/test/lisp/progmodes/c-ts-mode-resources/indent.erts +++ b/test/lisp/progmodes/c-ts-mode-resources/indent.erts @@ -464,3 +464,17 @@ main (void) | } =-=-= + +Name: Empty Line (Block Start) + +=-= +int +main (void) +{ +| +=-= +int +main (void) +{ + | +=-=-=