;;; 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 "<doc>")
(insert (csharp--get-doc-comments-above node))
(insert "</doc>")
(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 <doc>
(beginning-of-buffer)
(kill-whole-line)
;; delete </doc>
(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