Skip to content

Commit 542f175

Browse files
authored
Merge pull request #563 from bemoody/indent-doc
Indent example-code blocks in comments
2 parents 3bd0863 + c0cdf44 commit 542f175

File tree

2 files changed

+210
-1
lines changed

2 files changed

+210
-1
lines changed

rust-mode-tests.el

+92
Original file line numberDiff line numberDiff line change
@@ -887,6 +887,98 @@ struct A {
887887
"
888888
))
889889

890+
;; When point is inside an example code block, indent-for-tab-command
891+
;; should reindent the example code.
892+
(ert-deftest indent-inside-doc-example ()
893+
(rust-test-manip-code
894+
"
895+
/// ```
896+
/// if 2 + 2 == 4 {
897+
/// success();
898+
/// }
899+
/// ```
900+
"
901+
34
902+
#'indent-for-tab-command
903+
"
904+
/// ```
905+
/// if 2 + 2 == 4 {
906+
/// success();
907+
/// }
908+
/// ```
909+
"
910+
38))
911+
912+
;; Inside example code blocks, hidden lines starting with "# " should
913+
;; be indented as if the "# " wasn't there.
914+
(ert-deftest indent-inside-doc-example-hidden-code ()
915+
(rust-test-manip-code
916+
"
917+
/// ```
918+
/// # if 2 + 2 == 4 {
919+
/// # success();
920+
/// # }
921+
/// ```
922+
"
923+
36
924+
#'indent-for-tab-command
925+
"
926+
/// ```
927+
/// # if 2 + 2 == 4 {
928+
/// # success();
929+
/// # }
930+
/// ```
931+
"
932+
42))
933+
934+
;; Inside example code blocks, hidden lines starting with "# "
935+
;; shouldn't affect indentation of non-hidden lines.
936+
(ert-deftest indent-inside-doc-example-with-hidden-block ()
937+
(rust-test-manip-code
938+
"
939+
/// ```
940+
/// # if 2 + 2 == 4 {
941+
/// success();
942+
/// # }
943+
/// ```
944+
"
945+
40
946+
#'indent-for-tab-command
947+
"
948+
/// ```
949+
/// # if 2 + 2 == 4 {
950+
/// success();
951+
/// # }
952+
/// ```
953+
"
954+
36))
955+
956+
;; When point is outside the comment, indent-for-tab-command should
957+
;; reindent the comment line without affecting its contents.
958+
(ert-deftest indent-outside-doc-example ()
959+
(rust-test-manip-code
960+
"
961+
impl Foo {
962+
/// ```
963+
/// if 2 + 2 == 4 {
964+
/// success();
965+
/// }
966+
/// ```
967+
}
968+
"
969+
49
970+
#'indent-for-tab-command
971+
"
972+
impl Foo {
973+
/// ```
974+
/// if 2 + 2 == 4 {
975+
/// success();
976+
/// }
977+
/// ```
978+
}
979+
"
980+
53))
981+
890982
(defconst rust-test-motion-string
891983
"
892984
fn fn1(arg: i32) -> bool {

rust-prog-mode.el

+118-1
Original file line numberDiff line numberDiff line change
@@ -567,7 +567,7 @@ buffer."
567567
;; foo.bar
568568
(t (funcall skip-dot-identifier)))))))
569569

570-
(defun rust-mode-indent-line ()
570+
(defun rust-mode--indent-line ()
571571
(interactive)
572572
(let ((indent
573573
(save-excursion
@@ -769,6 +769,123 @@ buffer."
769769
(save-excursion (= (progn (goto-char pos1) (line-end-position))
770770
(progn (goto-char pos2) (line-end-position)))))
771771

772+
(defun rust-mode-indent-line ()
773+
"Indent the current line, and indent code examples in comments.
774+
775+
Indent the current line as `rust-mode-indent-line' does. If
776+
point is inside a block comment containing a Markdown code
777+
example (delimited by triple backquotes), then also indent the
778+
current line within the code example."
779+
(interactive)
780+
781+
;; First, reindent the current line.
782+
(rust-mode--indent-line)
783+
784+
;; If point is inside a comment:
785+
(let ((ppss (syntax-ppss)))
786+
(when (nth 4 ppss)
787+
(rust-with-comment-fill-prefix
788+
(lambda ()
789+
(let* ((orig-buf (current-buffer))
790+
(orig-point (point))
791+
(orig-eol (line-end-position))
792+
(orig-bol (line-beginning-position))
793+
(orig-mode major-mode)
794+
(com-start (nth 8 ppss))
795+
(com-prefix (replace-regexp-in-string "\\s-*\\'" ""
796+
(or fill-prefix "")))
797+
(com-re (regexp-quote com-prefix))
798+
(cb-re (concat "^" com-re "\\(\\s-*\\)```"))
799+
cb-start cb-pad cb-hidden-marker indented rel-point)
800+
801+
;; If point is within the prefix (not inside the comment
802+
;; body), don't do anything fancy.
803+
(when (>= orig-point (+ orig-bol (length com-prefix)))
804+
(save-excursion
805+
;; If we're in a // comment block, use the fill prefix to
806+
;; find the start of the block. If we're in a /*
807+
;; comment, the start is determined by ppss.
808+
(when (string-match "^\\s-*//" com-prefix)
809+
(setq com-start orig-bol)
810+
(while (and (= (forward-line -1) 0)
811+
(looking-at com-re))
812+
(setq com-start (point))))
813+
814+
;; Search for ``` lines within the comment block, and
815+
;; identify the start of the current code block if any.
816+
(goto-char com-start)
817+
(while (re-search-forward cb-re orig-bol t)
818+
(setq cb-start (unless cb-start (line-end-position))
819+
cb-pad (match-string 1))))
820+
821+
(when cb-start
822+
;; We're inside a code block. Copy preceding contents to
823+
;; a temporary buffer.
824+
(with-temp-buffer
825+
(insert-buffer-substring orig-buf cb-start orig-eol)
826+
(forward-char (- orig-point orig-eol))
827+
(save-excursion
828+
;; For each line in the temporary buffer, remove
829+
;; the comment prefix, left padding if present, and
830+
;; hidden-line marker if present. For example, if
831+
;; the code block begins with:
832+
;;
833+
;; ^ /// ```$
834+
;;
835+
;; then trim lines as follows:
836+
;;
837+
;; ^ ///$ becomes ^$
838+
;; ^ /// let x = 2;$ becomes ^ let x = 2;$
839+
;; ^ /// # fn main() {$ becomes ^fn main() {$
840+
;;
841+
;; If the line we're indenting isn't a hidden line,
842+
;; then remove hidden lines completely - non-hidden
843+
;; lines are indented as if the hidden lines don't
844+
;; exist.
845+
(let ((trim-re (concat com-re "\\(?:" cb-pad "\\)?"
846+
"\\(\\s-*# \\)?")))
847+
(beginning-of-line)
848+
(if (and (looking-at trim-re) (match-beginning 1))
849+
(setq cb-hidden-marker "# ")
850+
(setq cb-hidden-marker ""
851+
trim-re (concat com-re "\\(?:" cb-pad "\\)?"
852+
"\\(\\s-*# .*\\)?")))
853+
854+
(goto-char (point-min))
855+
(while (not (eobp))
856+
(when (looking-at trim-re)
857+
(delete-region (point) (match-end 0)))
858+
(forward-line 1))))
859+
860+
;; Reindent the line. Copy local settings from the
861+
;; parent buffer, but disable indent-tabs-mode unless
862+
;; it is enabled in the parent buffer and the code
863+
;; block is already tab-aligned.
864+
(funcall orig-mode)
865+
(mapc (lambda (v)
866+
(when (custom-variable-p (or (car-safe v) v))
867+
(if (symbolp v)
868+
(makunbound (make-local-variable v))
869+
(set (make-local-variable (car v)) (cdr v)))))
870+
(buffer-local-variables orig-buf))
871+
(or (string-suffix-p "\t" cb-pad)
872+
(= 0 (length com-prefix) (length cb-pad))
873+
(setq-local indent-tabs-mode nil))
874+
(rust-mode--indent-line)
875+
876+
;; Extract the indented line and copy back into the
877+
;; original buffer.
878+
(setq rel-point (- (point) (point-max)))
879+
(beginning-of-line)
880+
(setq indented
881+
(concat com-prefix cb-pad cb-hidden-marker
882+
(buffer-substring (point) (point-max)))))
883+
(goto-char orig-eol)
884+
(unless (equal indented (buffer-substring orig-bol orig-eol))
885+
(delete-region orig-bol orig-eol)
886+
(insert indented))
887+
(forward-char rel-point)))))))))
888+
772889
;;; Font-locking definitions and helpers
773890

774891
(defun rust-next-string-interpolation (limit)

0 commit comments

Comments
 (0)