[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
- [elpa] externals/csharp-mode 11d92b9 379/459: Various fixes, (continued)
- [elpa] externals/csharp-mode 11d92b9 379/459: Various fixes, ELPA Syncer, 2021/08/22
- [elpa] externals/csharp-mode 58928af 383/459: Indent object creation expression, ELPA Syncer, 2021/08/22
- [elpa] externals/csharp-mode a90b875 384/459: Add yield and object init indentation rules, ELPA Syncer, 2021/08/22
- [elpa] externals/csharp-mode eb3e9de 385/459: Almost functional indentation, ELPA Syncer, 2021/08/22
- [elpa] externals/csharp-mode 512c858 386/459: Start extracting defcustoms, ELPA Syncer, 2021/08/22
- [elpa] externals/csharp-mode f94e5a7 387/459: More extracting, ELPA Syncer, 2021/08/22
- [elpa] externals/csharp-mode 323a028 388/459: More cleaning, ELPA Syncer, 2021/08/22
- [elpa] externals/csharp-mode 84a5daa 389/459: Should not need to autoload c_sharp, ELPA Syncer, 2021/08/22
- [elpa] externals/csharp-mode 4651004 391/459: Hassle with the defcustoms, ELPA Syncer, 2021/08/22
- [elpa] externals/csharp-mode 5ac7484 392/459: Add back better support for interfaces, ELPA Syncer, 2021/08/22
- [elpa] externals/csharp-mode 4eef364 394/459: Absorb tree-sitter-indent,
ELPA Syncer <=
- [elpa] externals/csharp-mode 8fdeedf 397/459: Indentation tweaking, ELPA Syncer, 2021/08/22
- [elpa] externals/csharp-mode 3d0b60c 399/459: Add a little documentation, ELPA Syncer, 2021/08/22
- [elpa] externals/csharp-mode 0de2bbc 400/459: Fix byte compilation errors, ELPA Syncer, 2021/08/22
- [elpa] externals/csharp-mode 5730c99 401/459: Split functionality to two files, ELPA Syncer, 2021/08/22
- [elpa] externals/csharp-mode 4eeccbd 402/459: Remove newly added tests, ELPA Syncer, 2021/08/22
- [elpa] externals/csharp-mode 1e37eaf 407/459: Add dependency to tree-sitter-indent, ELPA Syncer, 2021/08/22
- [elpa] externals/csharp-mode 00a3cd4 410/459: Add new url to csharp-tree-sitter.el, ELPA Syncer, 2021/08/22
- [elpa] externals/csharp-mode 91d5161 414/459: Minor fix after version 0.11.0 (#209), ELPA Syncer, 2021/08/22
- [elpa] externals/csharp-mode 8466428 413/459: Version bump to 0.11.0, ELPA Syncer, 2021/08/22
- [elpa] externals/csharp-mode 84ff0d0 415/459: Typo from PR-209., ELPA Syncer, 2021/08/22