;;; csharp-yasnippet.el --- C# utility functions for yasnippet (provide 'csharp-yasnippet) (require 'treesit) (require 's) (defun csharp--get-csproj-in-directory (dir) (car (directory-files dir :mach-regexp ".*\\.csproj" :count 1))) (defun csharp--find-csproj (dir) (csharp--get-csproj-in-directory (locate-dominating-file dir #'csharp--get-csproj-in-directory))) (defun csharp--find-current-csproj () (csharp--find-csproj buffer-file-name)) (defun csharp--extract-project-name () (interactive) (let ((csproj (csharp--find-current-csproj))) (file-name-base csproj))) (defun csharp-file-path-to-namespace () (interactive) (let* ((root (file-name-directory (csharp--find-current-csproj))) (base (file-name-nondirectory buffer-file-name)) (project-name (csharp--extract-project-name)) (subdirectory (replace-regexp-in-string "/" "\." (substring buffer-file-name (length root) (* -1 (length base))) t t))) (concat project-name (if (string= "" subdirectory) "" (concat "." (substring subdirectory 0 -1)))))) (defun ruther--treesit-current-node () "Get the node at current point." (treesit-node-at (point))) (defun ruther--treesit-search-upwards (node predicate) "Find node following NODE that matches PREDICATE." (if node (if (apply predicate (list node)) node (ruther--treesit-search-upwards (treesit-node-parent node) predicate)) nil)) (defvar csharp-documentable-nodes '((method_declaration . csharp--document-method-declaration))) (defun csharp--next-documentable-node () "Get the next node that can be documented" (let* ((current-node (ruther--treesit-current-node)) (predicate (lambda (node) (seq-contains-p (mapcar 'car csharp-documentable-nodes) (intern (treesit-node-type node)))))) (or (ruther--treesit-search-upwards current-node predicate) (treesit-search-forward current-node predicate)))) (defun csharp--get-doc-comment-nodes-above (node) "Get all comments above NODE." (let ((prev-node (treesit-node-prev-sibling node))) (if (and (string-match-p "///.*" (treesit-node-text prev-node)) (equal "comment" (treesit-node-type prev-node))) (cons prev-node (csharp--get-doc-comment-nodes-above prev-node)) nil))) (defun csharp--get-doc-comments-above-region (node) "Get region with all the documentation comments" (let ((nodes (csharp--get-doc-comment-nodes-above node))) (if nodes (cons (seq-min (mapcar 'treesit-node-start nodes)) (seq-max (mapcar 'treesit-node-end nodes))) nil))) (defun csharp--get-doc-comments-above (node) "Get documentation comments above the given NODE as a string." (let* ((doc-comment-nodes (reverse (csharp--get-doc-comment-nodes-above node))) (doc-comments (mapcar 'treesit-node-text doc-comment-nodes))) (s-trim (substring-no-properties (apply 'concat (mapcar (lambda (comment) (concat (s-trim (substring comment 3)) "\n")) doc-comments)))))) (defun csharp--doc-comments-xml (node) "Get parsed documentation comments above the given NODE." (list (seq-filter (lambda (node) (not (stringp node))) (car (with-temp-buffer (insert "") (insert (csharp--get-doc-comments-above node)) (insert "") (xml-parse-region)))))) (defun csharp--xml-search-children (xml predicate) (let ((str (apply 'concat (mapcar (lambda (child) (if (stringp child) child (with-temp-buffer (xml-print (list child)) (buffer-string)))) (xml-node-children (seq-first (seq-filter predicate (xml-node-children (car xml))))))))) (if (string-empty-p str) nil (list str)))) (defun csharp--xml-param-doc (xml param-name) (csharp--xml-search-children xml (lambda (node) (and (equal (car node) 'param) (equal (xml-get-attribute node 'name) param-name))))) (defun csharp--xml-type-param-doc (xml param-name) (csharp--xml-search-children xml (lambda (node) (and (equal (car node) 'typeparam) (equal (xml-get-attribute node 'name) param-name))))) (defun csharp--xml-node-doc (xml node-name) (csharp--xml-search-children xml (lambda (node) (and (equal (car node) node-name))))) (defun csharp--parameter-names (parameters name) "Get names of parameters of a C# method located at METHOD-NODE." (mapcar (lambda (parameter) (substring-no-properties (treesit-node-text (treesit-node-child-by-field-name (car parameter) "name")))) (when parameters (cdr (treesit-induce-sparse-tree parameters (lambda (node) (equal name (treesit-node-type node)))))))) (defun csharp--method-parameters (method-node) "Get names of parameters of a C# method located at METHOD-NODE." (csharp--parameter-names (treesit-node-child-by-field-name method-node "parameters") "parameter")) (defun csharp--method-type-parameters (method-node) "Get names of parameters of a C# method located at METHOD-NODE." (csharp--parameter-names (treesit-node-child-by-field-name method-node "type_parameters") "type_parameter")) (defun csharp--method-return-type (method-node) "Get names of parameters of a C# method located at METHOD-NODE." (substring-no-properties (treesit-node-text (treesit-node-child-by-field-name method-node "returns")))) (defun csharp--method-void-p (method-node) "Get names of parameters of a C# method located at METHOD-NODE." (equal "void" (csharp--method-return-type method-node))) (defun csharp--method-declaration-documentation (method-node) (let ((xml (csharp--doc-comments-xml method-node))) `((doc nil (summary nil ,@(or (csharp--xml-node-doc xml 'summary) '("\n\n"))) ,@(if (csharp--xml-node-doc xml 'remarks) `((remarks nil ,@(csharp--xml-node-doc xml 'remarks))) '()) ,@(mapcar (lambda (parameter) `(param ((name . ,parameter)) ,@(or (csharp--xml-param-doc xml parameter) '("")))) (csharp--method-parameters method-node)) ,@(mapcar (lambda (parameter) `(typeparam ((name . ,parameter)) ,@(or (csharp--xml-type-param-doc xml parameter) '("")))) (csharp--method-type-parameters method-node)) ,@(if (csharp--method-void-p method-node) '() `((returns nil ,@(or (csharp--xml-node-doc xml 'returns) '(""))))))))) (defun csharp--method-declaration-documentation-string (method-node) (let ((docs (csharp--method-declaration-documentation method-node))) (with-temp-buffer (xml-print docs) ;; delete (beginning-of-buffer) (kill-whole-line) ;; delete (end-of-buffer) (kill-whole-line) (goto-char (point-max)) ;; Remove the trailing newline (delete-char -1) ;; (xml-mode) ;; (evil-indent (point-min) (point-max)) (goto-char (point-min)) (replace-regexp "^ <" "<") (replace-regexp ">$" ">") (goto-char (point-min)) (replace-regexp "<" "<") (goto-char (point-min)) (replace-regexp ">" ">") (goto-char (point-min)) (replace-regexp """ "\"") (goto-char (point-min)) (replace-regexp "^" "/// ") (substring-no-properties (buffer-string))))) (defun csharp--document-method-declaration (method-node) (let ((current-docs (csharp--get-doc-comments-above-region method-node)) (new-docs (csharp--method-declaration-documentation-string method-node))) (save-excursion (when current-docs (kill-region (car current-docs) (cdr current-docs)) (goto-char (car current-docs))) (unless current-docs (goto-char (treesit-node-start method-node)) (beginning-of-line) (newline) (forward-line -1) (setq-local current-docs (cons (point) 0))) (insert new-docs) (evil-indent (car current-docs) (point))))) (defun csharp--document-node (node) (apply (alist-get (intern (treesit-node-type node)) csharp-documentable-nodes) (list node))) (defun csharp-document-at-point () (interactive) (let* ((node (csharp--next-documentable-node))) (if node (csharp--document-node node) nil))) (provide 'test) ;;; test.el ends here ;;; csharp-yasnippet.el ends here