emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[elpa] externals/csharp-mode 4eef364 394/459: Absorb tree-sitter-indent


From: ELPA Syncer
Subject: [elpa] externals/csharp-mode 4eef364 394/459: Absorb tree-sitter-indent
Date: Sun, 22 Aug 2021 14:00:08 -0400 (EDT)

branch: externals/csharp-mode
commit 4eef364b4db089aa71b1e917cce7ad777bfd622a
Author: Theodor Thornhill <theo@thornhill.no>
Commit: Theodor Thornhill <theo@thornhill.no>

    Absorb tree-sitter-indent
    
    Contribute upstream anyways
---
 README.org            |   9 +-
 csharp-mode-indent.el | 446 ++++++++++++++++++++++++++++++++++++++++++++++++++
 csharp-mode.el        |  52 +-----
 3 files changed, 452 insertions(+), 55 deletions(-)

diff --git a/README.org b/README.org
index 4b9fcb9..a234d7e 100644
--- a/README.org
+++ b/README.org
@@ -4,8 +4,7 @@
 
 * csharp-mode
 
-This is a mode for editing C# in emacs. It's based on cc-mode, v5.30.3 and 
above.
-
+This is a mode for editing C# in emacs. It's using 
[[https://github.com/ubolonton/emacs-tree-sitter][tree-sitter]] for 
highlighting and indentation.
 ** Main features
 
 - font-lock and indent of C# syntax including:
@@ -17,9 +16,7 @@ This is a mode for editing C# in emacs. It's based on 
cc-mode, v5.30.3 and above
   - anonymous functions and methods
   - verbatim literal strings (those that begin with @)
   - generics 
-- automagic code-doc generation when you type three slashes.
 - intelligent insertion of matched pairs of curly braces.
-- imenu indexing of C# source, for easy menu-based navigation. 
 - compilation-mode support for msbuild, devenv and xbuild.
 
 ** Usage
@@ -44,10 +41,10 @@ To do so, add the following to your .emacs-file:
   (add-hook 'csharp-mode-hook 'my-csharp-mode-hook)
 #+END_SRC
 
-For further mode-specific customization, ~M-x customize-group RET csharp RET~ 
will show available settings with documentation. For configuring ~cc-mode~ 
settings (which csharp-mode derives from) see the 
[[https://www.gnu.org/software/emacs/manual/html_mono/ccmode.html][cc-mode 
manual]].
+For further mode-specific customization, ~M-x customize-group RET csharp RET~ 
will show available settings with documentation.
 
 For more advanced and IDE-like functionality we recommend using csharp-mode 
together
-with [[https://github.com/OmniSharp/omnisharp-emacs][Omnisharp-Emacs]].
+with [[https://github.com/emacs-lsp/lsp-mode][lsp-mode]] or 
[[https://github.com/joaotavora/eglot][eglot]]
 
 * Attribution
 
diff --git a/csharp-mode-indent.el b/csharp-mode-indent.el
new file mode 100644
index 0000000..03ec002
--- /dev/null
+++ b/csharp-mode-indent.el
@@ -0,0 +1,446 @@
+;;; csharp-mode-indent.el --- Provide indentation with a Tree-sitter backend 
-*- lexical-binding: t; -*-
+
+;; Copyright © 2020  Felipe Lema
+
+;; Author: Felipe Lema <felipelema@mortemale.org>
+;; Maintainer : Theodor Thornhill <theo@thornhill.no>
+;; Keywords: convenience, internal
+;; Package-Requires: ((emacs "26.1") (tree-sitter "0.12.1") (seq "2.21"))
+;; Version: 0.1
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Code:
+(require 'cl-lib)
+(require 'seq)
+(require 'tree-sitter)
+
+(defgroup csharp-mode-indent nil "Indent lines using Tree-sitter as backend"
+  :group 'tree-sitter)
+
+(defcustom csharp-mode-indent-offset 4
+  "Indent offset for csharp-mode"
+  :group 'csharp)
+
+(defcustom csharp-mode-indent-scopes
+  '((indent-all . ;; these nodes are always indented
+                (accessor_declaration
+                 break_statement
+                 "."))
+    (indent-rest . ;; if parent node is one of these and node is not first → 
indent
+                 (
+                  switch_section
+                  ))
+    (indent-body . ;; if parent node is one of these and current node is in 
middle → indent
+                 (block
+                  anonymous_object_creation_expression
+                  enum_member_declaration_list
+                  initializer_expression
+                  expression_statement
+                  declaration_list
+                  switch_body))
+
+    (paren-indent . ;; if parent node is one of these → indent to paren opener
+                  ())
+    (align-char-to . ;; chaining char → node types we move parentwise to find 
the first chaining char
+                   ())
+    (aligned-siblings . ;; siblings (nodes with same parent) should be aligned 
to the first child
+                      (parameter))
+
+    (multi-line-text . ;; if node is one of these, then don't modify the indent
+                     ;; this is basically a peaceful way out by saying "this 
looks like something
+                     ;; that cannot be indented using AST, so best I leave it 
as-is"
+                     (comment
+                      preprocessor_call
+                      labeled_statement))
+    (outdent . ;; these nodes always outdent (1 shift in opposite direction)
+             (;; "}"
+              case_switch_label
+              ))
+    )
+  "Scopes for indenting in C#."
+  :type 'sexp)
+
+;;;; Private functions
+(defun csharp-mode-indent--node-is-indent-all (node)
+  "Non-nil if NODE type is in indent-all group.
+
+Nodes in this group will be always +1 indentend."
+  (let-alist csharp-mode-indent-scopes
+    (member (tsc-node-type node)
+            .indent-all)))
+
+(defun csharp-mode-indent--node-is-indent-rest (node)
+  "Non-nil if NODE type is in indent-rest group.
+
+Nodes in this group will +1 indentend if they are a non-first
+child of parent node."
+  (let-alist csharp-mode-indent-scopes
+    (member (tsc-node-type node)
+            .indent-rest)))
+
+(defun csharp-mode-indent--node-is-indent-body (node)
+  "Non-nil if NODE type is in indent-body group.
+
+Nodes in this group will +1 indentend if they are both a
+non-first child of and non-last child of parent node."
+  (let-alist csharp-mode-indent-scopes
+    (member (tsc-node-type node)
+            .indent-body)))
+
+(defun csharp-mode-indent--node-is-multi-line-text (node)
+  "Non-nil if NODE type is in indent-rest group.
+
+Nodes in this group will keep their current indentation"
+  (let-alist csharp-mode-indent-scopes
+    (member (tsc-node-type node)
+            .multi-line-text)))
+
+(defun csharp-mode-indent--node-is-aligned-sibling (node)
+  "Non-nil if NODE type is in aligned-siblings group.
+
+Nodes in this group will be aligned to the column of the first sibling."
+  (let-alist csharp-mode-indent-scopes
+    (member (tsc-node-type node)
+            .aligned-siblings)))
+
+(defun csharp-mode-indent--highest-node-at-position (position)
+  "Get the node at buffer POSITION that's at the highest level.
+
+POSITION is a byte position in buffer like \\(point-min\\)."
+  (save-excursion
+    (goto-char position)
+    ;; maybe implement this as a cl-loop
+    (let* ((current-node (tree-sitter-node-at-point)))
+      ;; move upwards until we either don't have aparent node
+      ;; or we moved out of line
+      (while (and
+             current-node
+             (when-let* ((parent-node (tsc-get-parent current-node)))
+                (when (and ;; parent and current share same position
+                       (eq (tsc-node-start-byte parent-node)
+                           (tsc-node-start-byte current-node)))
+                 ;; move upwards to the parent node
+                 (setq current-node parent-node)))))
+      current-node)))
+
+(defun csharp-mode-indent--parentwise-path (node)
+  "Get list of nodes by moving parent-wise starting at NODE.
+
+The last element in returned path is NODE."
+  (let ((next-parent-node (tsc-get-parent node))
+        (path
+         (list node)))
+    (while next-parent-node
+      ;; collect
+      (push next-parent-node path)
+      ;; move to next iteration
+      (setq next-parent-node (tsc-get-parent next-parent-node)))
+    path))
+
+(defun csharp-mode-indent--node-is-paren-indent (node)
+  "Non-nil if NODE type is in paren-indent group.
+
+Child nodes in this group will be indentend to the paren opener
+column."
+  (let-alist csharp-mode-indent-scopes
+    (member (tsc-node-type node)
+            .paren-indent)))
+
+(defun csharp-mode-indent--chain-column (current-node align-char-to-alist 
parentwise-path)
+  "When node is in a chain call, return column to align each call.
+
+CURRENT-NODE current node being indented
+ALIGN-CHAR-TO-ALIST char → group of node types we can move within when 
searching
+for the first chain char.
+This group is supposed to contain all node types conformed by a chain.
+PARENTWISE-PATH nodes from CURRENT-NODE to tree root (\"document\")
+
+Returns a column to indent to or nil if no such column can / should be applied.
+
+Reads text from current buffer."
+  (let ((first-character-for-current-node
+         (string-to-char
+          (tsc-node-text current-node))))
+    (when-let* ((last-parent-belongs-to
+                 (alist-get first-character-for-current-node
+                            align-char-to-alist))
+                (last-parent-belonging-to
+                 (thread-last parentwise-path
+                   (reverse) ;; path starts at (ts-parent current-node)
+                   (cdr) ;; skip current-node
+                   ;; walk within allowed boundaries
+                   (seq-take-while
+                    (lambda (node)
+                      (member (tsc-node-type node)
+                              last-parent-belongs-to)))
+                   (seq-first)))
+                (first-char-position-within-last-parent-node
+                 ;; naive search, could be updated later
+                 ;; this may detect wrong column-char with something like 
⎡a(should().ignore().this)\n.b()\n.c()⎦
+                 (save-excursion
+                   (goto-char
+                    (tsc-node-start-byte last-parent-belonging-to))
+                   (search-forward-regexp
+                    (regexp-quote
+                     (char-to-string
+                      first-character-for-current-node))
+                    (tsc-node-end-byte current-node)
+                    t)
+                   (- (point) 1)))
+                (end-of-parent-line-pos
+                 (save-excursion
+                   (goto-char
+                    (tsc-node-start-byte last-parent-belonging-to))
+                   (line-end-position))))
+      (when (and (numberp first-char-position-within-last-parent-node)
+                 ;; char is within parent line
+                 (or (< first-char-position-within-last-parent-node
+                        end-of-parent-line-pos)
+                     ;; char is the first in its line
+                     (eq first-char-position-within-last-parent-node
+                         (save-excursion
+                           (goto-char
+                            first-char-position-within-last-parent-node)
+                           (back-to-indentation)
+                           (point)))))
+        ;; indent to column, which is (char-pos - line-begin-pos)
+        (save-excursion
+          (goto-char first-char-position-within-last-parent-node)
+          (- first-char-position-within-last-parent-node
+             (line-beginning-position)))))))
+
+(defun csharp-mode-indent--first-sibling-column (current-node parent-node)
+  "Column position for CURRENT-NODE's first sibling.
+
+If CURRENT-NODE belongs to the aligned-siblings group, will look up the first
+sibling in same group \\(running through PARENT-NODE's children) and return
+its column.
+
+CSHARP-MODE-INDENT-SCOPES is used to test whether
+CURRENT-NODE belongs to the aligned-siblings group."
+  (when (and parent-node
+             (csharp-mode-indent--node-is-aligned-sibling current-node))
+    (when-let* ((current-node-type
+                 (tsc-node-type current-node))
+                (first-sibling
+                 (cl-loop for ith-sibling = (tsc-get-nth-child parent-node 0)
+                          then (tsc-get-next-sibling ith-sibling)
+                          while (not (null ith-sibling))
+                          if (equal current-node-type
+                                    (tsc-node-type ith-sibling))
+                          return ith-sibling
+                          end))
+                (first-sibling-position
+                 (tsc-node-start-byte first-sibling)))
+      (when (not (tsc-node-eq current-node first-sibling))
+        (save-excursion
+          (goto-char first-sibling-position)
+          (- first-sibling-position
+             (line-beginning-position)))))))
+
+(cl-defun csharp-mode-indent--indents-in-path (parentwise-path original-column)
+  "Map PARENTWISE-PATH into indent instructions.
+
+Each element of the returned list is one of the following
+
+no-indent                         nothing to add to current column
+indent                            add one indent to current column
+outdent                           subtract one indent to current column
+\(column-indent . COLUMN)         match parent's parent opener column
+\(preserve . ORIGINAL-COLUMN)     preserve the column that was before
+
+What is checked to add an indent:
+- A node bolongs into the \"indent\" group in CSHARP-MODE-INDENT-SCOPES
+- Deterimen what group the node's parent belongs to, and whether the node
+is in a middle position.
+- A node belongs to the \"outdent\" group in CSHARP-MODE-INDENT-SCOPES
+- A node belongs to the \"column-indent\" group in CSHARP-MODE-INDENT-SCOPES"
+  (let ((last-node
+         (seq-elt
+          parentwise-path
+          (-
+           (length parentwise-path)
+           1))))
+    (thread-last parentwise-path
+      (seq-map
+       (lambda (current-node)
+         (let* ((previous-node
+                 (tsc-get-prev-sibling current-node))
+                (next-node
+                 (tsc-get-next-sibling current-node))
+                (parent-node
+                 (tsc-get-parent current-node))
+                (current-node-is-rest
+                 previous-node)
+                (current-node-is-middle-node
+                 (and current-node-is-rest next-node))
+                (current-node-must-indent
+                 (csharp-mode-indent--node-is-indent-all current-node))
+                (current-node-must-outdent
+                 (and
+                  (eq last-node current-node)
+                  (csharp-mode-indent--node-is-outdent current-node)))
+                (chain-column
+                 (csharp-mode-indent--chain-column
+                  current-node
+                  (let-alist csharp-mode-indent-scopes
+                    .align-char-to)
+                  parentwise-path))
+                (sibling-column
+                 (csharp-mode-indent--first-sibling-column
+                  current-node
+                  parent-node)))
+           (cond
+            ((numberp chain-column)
+             `(column-indent ,chain-column))
+            ((numberp sibling-column)
+             `(column-indent ,sibling-column))
+            ((csharp-mode-indent--node-is-multi-line-text current-node)
+             `(preserve . ,original-column))
+            ((and parent-node
+                  (csharp-mode-indent--node-is-paren-indent parent-node))
+             (let* ((paren-opener
+                     (tsc-node-start-byte parent-node))
+                    (paren-point
+                     (save-excursion
+                       (goto-char paren-opener)
+                       (point)))
+                    (beginning-of-line-point
+                     (save-excursion
+                       (goto-char paren-opener)
+                       (beginning-of-line 1)
+                       (point)))
+                    (paren-indenting-column
+                     (+ 1
+                        (- paren-point beginning-of-line-point))))
+               `(column-indent ,paren-indenting-column)))
+
+            ((or current-node-must-indent
+                 (and parent-node
+                      current-node-is-rest
+                      (csharp-mode-indent--node-is-indent-rest parent-node))
+                 (and parent-node
+                      current-node-is-middle-node
+                      (csharp-mode-indent--node-is-indent-body parent-node)))
+             (if current-node-must-outdent
+                 'no-indent ;; if it's an outdent, cancel
+               'indent))
+            (current-node-must-outdent
+             'outdent)
+            (t
+             'no-indent))))))))
+
+(defun csharp-mode-indent--node-is-outdent (node)
+  "Return non-nil if NODE outdents per SCOPES.
+
+NODE is tested if it belongs into the \"outdent\" group in SCOPES."
+  (let-alist csharp-mode-indent-scopes
+    (member (tsc-node-type node)
+            .outdent)))
+
+(defun csharp-mode-indent--updated-column (column indent)
+  "Return COLUMN after added indent instructions per INDENT.
+
+INDENT is one of `csharp-mode-indent--indents-in-path'.
+
+If \"1 indent\" is to be applied, then returned value is
+CSHARP-MODE-INDENT-OFFSET + INDENT."
+  (pcase indent
+    (`no-indent
+     column)
+    (`indent
+     (+ column csharp-mode-indent-offset))
+    (`outdent
+     (- column csharp-mode-indent-offset))
+    (`(column-indent ,paren-column)
+     paren-column)
+    (`(preserve . ,original-column)
+     original-column)
+    (_
+     (error "Unexpected indent instruction: %s" indent))))
+
+(cl-defun csharp-mode-indent--indent-column (original-column)
+  "Return the column the first non-whitespace char at POSITION should indent 
to.
+
+Collect indent instruction per AST with
+`csharp-mode-indent--indents-in-path', then apply instructions
+with `csharp-mode-indent--updated-column' using
+CSHARP-MODE-INDENT-OFFSET as step.
+
+See `csharp-mode-indent-line'.  ORIGINAL-COLUMN is forwarded to
+`csharp-mode-indent--indents-in-path'"
+  (let* ((indenting-node
+          (csharp-mode-indent--highest-node-at-position
+           (save-excursion
+             (back-to-indentation)
+             (point))))
+         (parentwise-path (csharp-mode-indent--parentwise-path indenting-node))
+         (indents-in-path
+          (csharp-mode-indent--indents-in-path parentwise-path
+                                               original-column)))
+    (seq-reduce #'csharp-mode-indent--updated-column
+                indents-in-path
+                0 ;; start at column 0
+                )))
+
+;;;; Public API
+
+;;;###autoload
+(defun csharp-mode-indent-line ()
+  "Use Tree-sitter as backend to indent current line."
+  ;;Use in buffer like so:
+
+  ;; (setq-local indent-line-function #'csharp-mode-indent-line).
+  (let* ((original-position
+          (point))
+         (first-non-blank-pos ;; see savep in `smie-indent-line'
+          (save-excursion
+            (forward-line 0)
+            (skip-chars-forward " \t")
+            (point)))
+         (should-save-excursion
+          (< first-non-blank-pos original-position))
+         (original-column
+          (abs (- (line-beginning-position)
+                  first-non-blank-pos)))
+         (new-column
+          (csharp-mode-indent--indent-column original-column)))
+    (when (numberp new-column)
+      (if should-save-excursion
+          (save-excursion (indent-line-to new-column))
+        (indent-line-to new-column)))))
+
+(defun csharp-mode-indent-line-and-debug ()
+  "Call `csharp-mode-indent-line' while printing useful info."
+  (let* ((line-str (thing-at-point 'line))
+         (position (point))
+         (indenting-node (csharp-mode-indent--highest-node-at-position
+                          position))
+         (parentwise-path (csharp-mode-indent--parentwise-path indenting-node))
+         (readable-parentwise-path
+          (seq-map 'tsc-node-type parentwise-path))
+         (tree-sitter-tree-before (tsc-tree-to-sexp tree-sitter-tree))
+         (column
+          (csharp-mode-indent-line)))
+    (message "csharp-mode-indent: Indented ⎡%s⎦ to ⎡%s⎦ (col %s) because of 
parentwise path of ⎡%s⎦ (while looking at ⎡%s⎦ & when tree is ⎡%s⎦)"
+             line-str
+             (thing-at-point 'line)
+             column
+             readable-parentwise-path
+             (tsc-node-type indenting-node)
+             tree-sitter-tree-before)))
+
+(provide 'csharp-mode-indent)
+;;; csharp-mode-indent.el ends here
diff --git a/csharp-mode.el b/csharp-mode.el
index 80bc80e..8798974 100644
--- a/csharp-mode.el
+++ b/csharp-mode.el
@@ -27,7 +27,7 @@
 
 (require 'tree-sitter)
 (require 'tree-sitter-hl)
-(require 'tree-sitter-indent)
+(require 'csharp-mode-indent)
 
 (require 'compile)
 
@@ -148,15 +148,6 @@
       (add-to-list 'compilation-error-regexp-alist-alist regexp)
       (add-to-list 'compilation-error-regexp-alist (car regexp)))))
 
-(defcustom csharp-mode-indent-offset 4
-  "Indent offset for csharp-mode"
-  :type 'number
-  :group 'csharp)
-
-(defcustom tree-sitter-indent-csharp-scopes nil
-  "Scopes for indenting in C#."
-  :type 'sexp)
-
 ;;;###autoload
 (add-to-list 'auto-mode-alist '("\\.cs\\'" . csharp-mode))
 
@@ -174,47 +165,10 @@
 Key bindings:
 \\{csharp-mode-map}"
   :group 'csharp
-  (setq-local tree-sitter-indent-offset csharp-mode-indent-offset)
-  (setq-local tree-sitter-indent-current-scopes
-              '((indent-all . ;; these nodes are always indented
-                            (accessor_declaration
-                             break_statement
-                             "."))
-                (indent-rest . ;; if parent node is one of these and node is 
not first → indent
-                             (
-                              switch_section
-                              ))
-                (indent-body . ;; if parent node is one of these and current 
node is in middle → indent
-                             (block
-                              anonymous_object_creation_expression
-                              enum_member_declaration_list
-                              initializer_expression
-                              expression_statement
-                              declaration_list
-                              switch_body))
-
-                (paren-indent . ;; if parent node is one of these → indent to 
paren opener
-                              ())
-                (align-char-to . ;; chaining char → node types we move 
parentwise to find the first chaining char
-                               ())
-                (aligned-siblings . ;; siblings (nodes with same parent) 
should be aligned to the first child
-                                  (parameter))
-
-                (multi-line-text . ;; if node is one of these, then don't 
modify the indent
-                                 ;; this is basically a peaceful way out by 
saying "this looks like something
-                                 ;; that cannot be indented using AST, so best 
I leave it as-is"
-                                 (comment
-                                  preprocessor_call
-                                  labeled_statement))
-                (outdent . ;; these nodes always outdent (1 shift in opposite 
direction)
-                         (;; "}"
-                          case_switch_label
-                          ))
-                )
-              )
+
   ;; (when (boundp 'electric-indent-inhibit)
   ;;   (setq electric-indent-inhibit t))
-  (setq-local indent-line-function #'tree-sitter-indent-line)
+  (setq-local indent-line-function #'csharp-mode-indent-line)
 
   ;; https://github.com/ubolonton/emacs-tree-sitter/issues/84
   (unless font-lock-defaults



reply via email to

[Prev in Thread] Current Thread [Next in Thread]