M init.el => init.el +8 -3
@@ 1367,6 1367,8 @@
:ensure nil
:custom
(treesit-font-lock-level 4)
+ (treesit-load-name-override-list
+ '((c-sharp "libtree-sitter-c_sharp" "tree_sitter_c_sharp")))
:config
(which-function-mode 1)
@@ 1440,14 1442,17 @@ minibuffer, even without explicitly focusing it."
(my-use-package csharp-mode
:ensure nil
- :after eglot
- :mode (("\\.cs\\'" . csharp-mode))
+ :after lsp-mode
+ :mode (("\\.cs\\'" . csharp-ts-mode))
+ :general
+ (my-local-leader csharp-mode-map
+ "d" '(csharp-document-at-point :wk "Document thing at point"))
:config
(my/indent-variable-mode-alist-add csharp-mode c-basic-offset)
(my/indent-variable-mode-alist-add csharp-ts-mode csharp-ts-mode-indent-offset))
(my-use-package csharp-yasnippet
- :commands (csharp-file-path-to-namespace))
+ :commands (csharp-file-path-to-namespace csharp-document-at-point))
;; Build solution
;; --debug / --release
M lisp/csharp-yasnippet.el => lisp/csharp-yasnippet.el +229 -1
@@ 1,6 1,8 @@
;;; 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
@@ 33,4 35,230 @@
""
(concat "." (substring subdirectory 0 -1))))))
-;;; csharp-yasnippet.el ends here>
\ No newline at end of file
+(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