diff --git a/pi-coding-agent-grammars.el b/pi-coding-agent-grammars.el index 466add2..06f5509 100644 --- a/pi-coding-agent-grammars.el +++ b/pi-coding-agent-grammars.el @@ -120,6 +120,56 @@ Each entry is (LANG URL REVISION [SOURCE-DIR]).") (seq-filter #'treesit-language-available-p pi-coding-agent--optional-grammars)) +(defconst pi-coding-agent--markdown-table-smoke-text + "| a | b |\n| - | - |\n| 1 | 2 |\n" + "Small table used to check Markdown grammar compatibility.") + +(defconst pi-coding-agent--markdown-table-smoke-query + '((pipe_table) @table + (pipe_table_cell) @cell + (pipe_table_row) @row) + "Markdown grammar query required by chat table rendering.") + +(defun pi-coding-agent--markdown-grammar-compatible-p () + "Return non-nil when the loaded Markdown grammar supports chat rendering." + (condition-case nil + (with-temp-buffer + (insert pi-coding-agent--markdown-table-smoke-text) + (let* ((parser (treesit-parser-create 'markdown)) + (root (treesit-parser-root-node parser)) + (captures (treesit-query-capture + root pi-coding-agent--markdown-table-smoke-query))) + (and (assq 'table captures) + (assq 'cell captures) + (assq 'row captures) + t))) + (error nil))) + +(defun pi-coding-agent--markdown-grammar-incompatible-p () + "Return non-nil when an installed Markdown grammar is incompatible." + (and (treesit-language-available-p 'markdown) + (not (pi-coding-agent--markdown-grammar-compatible-p)))) + +(defun pi-coding-agent--incompatible-markdown-grammar-message () + "Return the user-facing Markdown grammar compatibility warning." + (format "Incompatible Markdown tree-sitter grammar version loaded. Remove\ + old/system libtree-sitter-markdown from `treesit-extra-load-path', %s, or\ + system packages, then restart Emacs or run M-x pi-coding-agent-install-grammars." + (abbreviate-file-name (locate-user-emacs-file "tree-sitter")))) + +(defvar pi-coding-agent--markdown-grammar-warning-done nil + "Non-nil once the incompatible Markdown grammar warning was shown.") + +(defun pi-coding-agent--maybe-warn-incompatible-markdown-grammar () + "Warn once when the loaded Markdown tree-sitter grammar is incompatible." + (unless (or noninteractive pi-coding-agent--markdown-grammar-warning-done) + (when (pi-coding-agent--markdown-grammar-incompatible-p) + (setq pi-coding-agent--markdown-grammar-warning-done t) + (display-warning + 'pi-coding-agent + (pi-coding-agent--incompatible-markdown-grammar-message) + :warning)))) + ;;;; Installation (defun pi-coding-agent--install-grammars (grammars) @@ -146,6 +196,7 @@ A C compiler (gcc or cc) is required.\n\ (setq idx (1- idx)))) (when (> idx 0) (message "[pi-coding-agent] Installed %d/%d grammars." idx total)) + (setq pi-coding-agent--markdown-grammar-warning-done nil) idx)) (defcustom pi-coding-agent-essential-grammar-action 'prompt @@ -251,10 +302,19 @@ then offers to install the missing ones." (interactive) (let ((installed (pi-coding-agent--installed-optional-grammars)) (missing (pi-coding-agent--missing-optional-grammars)) - (essential-missing (pi-coding-agent--missing-essential-grammars))) - (if (and (null missing) (null essential-missing)) - (message "All %d tree-sitter grammars are installed. ✓" - (length installed)) + (essential-missing (pi-coding-agent--missing-essential-grammars)) + (markdown-incompatible + (pi-coding-agent--markdown-grammar-incompatible-p))) + (cond + (markdown-incompatible + (display-warning + 'pi-coding-agent + (pi-coding-agent--incompatible-markdown-grammar-message) + :warning)) + ((and (null missing) (null essential-missing)) + (message "All %d tree-sitter grammars are installed. ✓" + (length installed))) + (t (let ((buf (get-buffer-create "*pi-coding-agent-grammars*"))) (with-current-buffer buf (let ((inhibit-read-only t)) @@ -286,7 +346,7 @@ then offers to install the missing ones." (pi-coding-agent-install-grammars))))) (local-set-key "q" #'quit-window) (goto-char (point-min))) - (pop-to-buffer buf))))) + (pop-to-buffer buf)))))) (provide 'pi-coding-agent-grammars) ;;; pi-coding-agent-grammars.el ends here diff --git a/pi-coding-agent-ui.el b/pi-coding-agent-ui.el index 1269199..7ccfc04 100644 --- a/pi-coding-agent-ui.el +++ b/pi-coding-agent-ui.el @@ -1597,6 +1597,7 @@ Displays warnings for missing dependencies." (pi-coding-agent--pi-install-command)) :error)) (pi-coding-agent--maybe-install-essential-grammars) + (pi-coding-agent--maybe-warn-incompatible-markdown-grammar) (pi-coding-agent--maybe-install-optional-grammars)) ;;;; Startup Header diff --git a/test/pi-coding-agent-ui-test.el b/test/pi-coding-agent-ui-test.el index 9f196f5..f338e0f 100644 --- a/test/pi-coding-agent-ui-test.el +++ b/test/pi-coding-agent-ui-test.el @@ -1205,6 +1205,39 @@ Buffer is read-only with `inhibit-read-only' used for insertion. (pi-coding-agent--maybe-install-essential-grammars) (should-not installed)))) +;;; Markdown Grammar Compatibility + +(ert-deftest pi-coding-agent-test-incompatible-markdown-grammar-detected () + "Detect an installed Markdown grammar that lacks required table nodes." + (cl-letf (((symbol-function 'treesit-language-available-p) + (lambda (_lang &rest _) t)) + ((symbol-function 'pi-coding-agent--markdown-grammar-compatible-p) + (lambda () nil))) + (should (pi-coding-agent--markdown-grammar-incompatible-p)))) + +(ert-deftest pi-coding-agent-test-missing-markdown-grammar-not-incompatible () + "Missing Markdown grammar is handled by the missing-essential path." + (cl-letf (((symbol-function 'treesit-language-available-p) + (lambda (_lang &rest _) nil)) + ((symbol-function 'pi-coding-agent--markdown-grammar-compatible-p) + (lambda () nil))) + (should-not (pi-coding-agent--markdown-grammar-incompatible-p)))) + +(ert-deftest pi-coding-agent-test-incompatible-markdown-grammar-warns-once () + "Warn once when the loaded Markdown grammar is incompatible." + (let ((noninteractive nil) + (pi-coding-agent--markdown-grammar-warning-done nil) + (warnings nil)) + (cl-letf (((symbol-function 'pi-coding-agent--markdown-grammar-incompatible-p) + (lambda () t)) + ((symbol-function 'display-warning) + (lambda (_type msg &rest _) (push msg warnings)))) + (pi-coding-agent--maybe-warn-incompatible-markdown-grammar) + (pi-coding-agent--maybe-warn-incompatible-markdown-grammar) + (should (= 1 (length warnings))) + (should (string-match-p "Incompatible Markdown" (car warnings))) + (should (string-match-p "treesit-extra-load-path" (car warnings)))))) + ;;; Grammar Recipe Validation (ert-deftest pi-coding-agent-test-grammar-recipes-all-registered () @@ -1504,6 +1537,8 @@ a new grammar (e.g., `zig') appears in the missing set." (let ((msg nil)) (cl-letf (((symbol-function 'treesit-language-available-p) (lambda (_lang &rest _) t)) + ((symbol-function 'pi-coding-agent--markdown-grammar-compatible-p) + (lambda () t)) ((symbol-function 'message) (lambda (fmt &rest args) (setq msg (apply #'format fmt args))))) (pi-coding-agent-install-grammars) @@ -1515,6 +1550,8 @@ a new grammar (e.g., `zig') appears in the missing set." (cl-letf (((symbol-function 'treesit-language-available-p) (lambda (lang &rest _) (memq lang '(markdown markdown-inline python)))) + ((symbol-function 'pi-coding-agent--markdown-grammar-compatible-p) + (lambda () t)) ((symbol-function 'pop-to-buffer) #'ignore)) (unwind-protect @@ -1540,6 +1577,8 @@ a new grammar (e.g., `zig') appears in the missing set." "Interactive command highlights missing essential grammars prominently." (cl-letf (((symbol-function 'treesit-language-available-p) (lambda (_lang &rest _) nil)) + ((symbol-function 'pi-coding-agent--markdown-grammar-compatible-p) + (lambda () t)) ((symbol-function 'pop-to-buffer) #'ignore)) (unwind-protect @@ -1553,6 +1592,22 @@ a new grammar (e.g., `zig') appears in the missing set." (when-let* ((buf (get-buffer "*pi-coding-agent-grammars*"))) (kill-buffer buf))))) +(ert-deftest pi-coding-agent-test-install-grammars-command-warns-incompatible-markdown () + "Interactive command warns when an installed Markdown grammar is incompatible." + (let ((warning-text nil) + (msg nil)) + (cl-letf (((symbol-function 'treesit-language-available-p) + (lambda (_lang &rest _) t)) + ((symbol-function 'pi-coding-agent--markdown-grammar-compatible-p) + (lambda () nil)) + ((symbol-function 'display-warning) + (lambda (_type msg &rest _) (setq warning-text msg))) + ((symbol-function 'message) + (lambda (fmt &rest args) (setq msg (apply #'format fmt args))))) + (pi-coding-agent-install-grammars) + (should (string-match-p "Incompatible Markdown" warning-text)) + (should-not msg)))) + ;;; CI Install Script Smoke Test (ert-deftest pi-coding-agent-test-ci-install-script-loads () @@ -1569,16 +1624,20 @@ Catches wiring bugs like requiring deleted modules." ;;; check-dependencies (ert-deftest pi-coding-agent-test-check-dependencies-calls-grammar-checks () - "check-dependencies invokes both grammar check functions." + "check-dependencies invokes grammar install and compatibility checks." (let ((essential-called nil) + (compatibility-called nil) (optional-called nil)) (cl-letf (((symbol-function 'pi-coding-agent--check-pi) (lambda () t)) ((symbol-function 'pi-coding-agent--maybe-install-essential-grammars) (lambda () (setq essential-called t))) + ((symbol-function 'pi-coding-agent--maybe-warn-incompatible-markdown-grammar) + (lambda () (setq compatibility-called t))) ((symbol-function 'pi-coding-agent--maybe-install-optional-grammars) (lambda () (setq optional-called t)))) (pi-coding-agent--check-dependencies) (should essential-called) + (should compatibility-called) (should optional-called)))) ;;; State response