From 80851aca01b4689ad98f1a6fa03c05bc0e3e74f2 Mon Sep 17 00:00:00 2001 From: Mois Moshev Date: Sat, 8 Jun 2024 02:57:31 +0300 Subject: [PATCH 1/4] Implement indentation --- d2-mode.el | 158 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 153 insertions(+), 5 deletions(-) diff --git a/d2-mode.el b/d2-mode.el index c308693..39af039 100644 --- a/d2-mode.el +++ b/d2-mode.el @@ -122,14 +122,161 @@ (org-babel-eval cmd "") nil)) -(defun d2--locate-declaration (str) +(defun d2--locate-declaration (str tag) "Locate a certain declaration and return the line difference and indentation. -STR is the declaration." +STR is the declaration. +TAG is a symbol, which is prepended to the declaration data." (let ((l (line-number-at-pos))) (save-excursion (if (re-search-backward str (point-min) t) - (cons (- l (line-number-at-pos)) (current-indentation)) - (cons -1 -1))))) + (cons tag (cons (- l (line-number-at-pos)) (current-indentation))) + (cons tag (cons -1 -1)))))) + +(defun d2--combine-tokens-by-level (tokens) + "Combine TOKENS that are at the same level. +Collect the tags from both into a list. +Use the newer token's line and column." + (reverse + (cl-reduce (lambda (acc el) + (if (and (not (null acc)) + (equal (d2--decl-line (car acc)) + (d2--decl-line el))) + ;; when tokens are on the same line, combine them + (progn + (setf (car acc) + ;; we don't care about duplicate tags + (cons (cons (d2--decl-tag el) (d2--decl-tag (car acc))) + (cons (d2--decl-line el) + (d2--decl-column el)))) + acc) + ;; otherwise add a new token; set its tag to be a list + (cons (cons (list (d2--decl-tag el)) + (cons (d2--decl-line el) + (d2--decl-column el))) + acc))) + tokens + :initial-value ()))) + +(defun d2--sort-tokens-by-line (tokens) + "Sort TOKENS by line number." + (sort tokens + :key (lambda (token) + (d2--decl-line token)))) + +(defun d2--filter-not-found-tokens (tokens) + "Filter TOKENS that were not detected. +They have a line number of -1" + (cl-remove-if + (lambda (token) (< (d2--decl-line token) 0)) + tokens)) + +(defun d2--parse-from-line () + "Parse by starting at current line and searching backward." + (let ((node (d2--locate-declaration + (rx (group (one-or-more (any alnum "_"))) + (? (group ":" + (one-or-more space))) + (? (group + (one-or-more (not (any ?{ ?})))))) + 'node)) + (subnode-start (d2--locate-declaration + (rx (group (one-or-more (any alnum "_" "<->" + "->" "--" "<-")) + (? (group ":" (one-or-more space))) + (? (group (one-or-more (not (any ?{ ?}))))) + "{" + )) + 'subnode)) + (end (d2--locate-declaration "^ *} *$" + 'end)) + (connection (d2--locate-declaration + (rx (group (one-or-more (any alnum "_" "-")) + (any "->" + "<->" + "--" + "<-") + (? (seq + ":" + (one-or-more space))) + (? (one-or-more graph)))) + 'connection))) + (list node subnode-start end connection))) + +;;; declaration part handling +(defun d2--decl-tag (decl) (car decl)) +(defun d2--decl-line (decl) (cadr decl)) +(gv-define-setter d2--decl-line (val decl) `(setf (cadr ,decl) ,val)) +(defun d2--decl-column (decl) (cddr decl)) +(defun d2--decl-tags-contain (decl tag) + (seq-find (lambda (tag2) + (equal tag2 tag)) + (d2--decl-tag decl))) + +(defun d2--calculate-desired-indentation () + (save-excursion + (end-of-line) + ;; sort tokens by line number (distance from current line) + ;; and calculate the final indentation + (let* ((ordered-tokens (d2--combine-tokens-by-level + (d2--sort-tokens-by-line + (append (d2--filter-not-found-tokens (d2--parse-from-line)) + ;; also collect tokens starting at previous line + ;; otherwise successive tokens of the same type + ;; may not be detected + (if (equal (line-number-at-pos) 1) + () + (progn (forward-line -1) + (end-of-line) + (mapcar (lambda (token) + ;; increment line number + ;; to make it relative to starting line + (cl-incf (d2--decl-line token)) + token) + (d2--filter-not-found-tokens + (d2--parse-from-line))))))))) + (current-token (car ordered-tokens)) + (previous-token (cadr ordered-tokens))) + (message "tokens %s" ordered-tokens) + (cond ((and (d2--decl-tags-contain current-token 'node) + (null previous-token)) + 0) + + ((and (d2--decl-tags-contain current-token 'node) + (d2--decl-tags-contain previous-token 'subnode)) + (+ 4 (d2--decl-column previous-token))) + + ((and (d2--decl-tags-contain current-token 'subnode) + (d2--decl-tags-contain previous-token 'subnode)) + (+ 4 (d2--decl-column previous-token))) + + + ((and (d2--decl-tags-contain current-token 'node) + (d2--decl-tags-contain previous-token 'node)) + (d2--decl-column previous-token)) + + ((and (d2--decl-tags-contain current-token 'node) + (d2--decl-tags-contain previous-token 'end)) + (d2--decl-column previous-token)) + + ((and (d2--decl-tags-contain current-token 'end) + (d2--decl-tags-contain previous-token 'end)) + (max (- (d2--decl-column previous-token) 4) 0)) + + ((and (d2--decl-tags-contain current-token 'end) + (d2--decl-tags-contain previous-token 'subnode)) + (d2--decl-column previous-token)) + + ((and (d2--decl-tags-contain current-token 'end) + (d2--decl-tags-contain previous-token 'node)) + (max (- (d2--decl-column previous-token) 4) 0)) + + (t (progn (message "uknown syntax %s" current-token) + (d2--decl-column current-token))) + )))) + +(defun d2-indent-line () + (interactive) + (indent-line-to (d2--calculate-desired-indentation))) (defun d2-compile () "Compile the current d2 file using d2." @@ -220,7 +367,8 @@ Optional argument BROWSE whether to open the browser." (setq-local font-lock-defaults '(d2-font-lock-keywords)) (setq-local comment-start "#") (setq-local comment-end "") - (setq-local comment-start-skip "#")) + (setq-local comment-start-skip "#") + (setq-local indent-line-function 'd2-indent-line)) (provide 'd2-mode) From 5835643bdb931a38cbcff5ee22bb9e05395a94d4 Mon Sep 17 00:00:00 2001 From: Mois Moshev Date: Fri, 5 Jul 2024 18:51:10 +0300 Subject: [PATCH 2/4] Add comments. Address linter errors. --- d2-mode.el | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/d2-mode.el b/d2-mode.el index 39af039..77e9194 100644 --- a/d2-mode.el +++ b/d2-mode.el @@ -184,8 +184,7 @@ They have a line number of -1" "->" "--" "<-")) (? (group ":" (one-or-more space))) (? (group (one-or-more (not (any ?{ ?}))))) - "{" - )) + "{")) 'subnode)) (end (d2--locate-declaration "^ *} *$" 'end)) @@ -203,16 +202,33 @@ They have a line number of -1" (list node subnode-start end connection))) ;;; declaration part handling -(defun d2--decl-tag (decl) (car decl)) -(defun d2--decl-line (decl) (cadr decl)) -(gv-define-setter d2--decl-line (val decl) `(setf (cadr ,decl) ,val)) -(defun d2--decl-column (decl) (cddr decl)) +(defun d2--decl-tag (decl) + "Get tag from the declaration DECL. It is the list head." + (car decl)) +(defun d2--decl-line (decl) + "Get the text of the declaration DECL. It is the second item in the list." + (cadr decl)) +(gv-define-setter d2--decl-line (val decl) + "Allow line VAL to be setf in declaration DECL." + `(setf (cadr ,decl) ,val)) +(defun d2--decl-column (decl) + "Get the column number from DECL. It is the third item." + (cddr decl)) (defun d2--decl-tags-contain (decl tag) + "Check if any of the tags in DECL matches TAG." (seq-find (lambda (tag2) (equal tag2 tag)) (d2--decl-tag decl))) (defun d2--calculate-desired-indentation () + "Calculate indentation of the current line. +Scan the tokens backwards and accumulate a list. +Only one token from each type is in this list. +To accommodate nested structures, we scan twice. +This way two tokens of the same type can be detected in sequence. +Once this information is collected, the indentation is chosen based +on the types of the current and previous token, +and the indentation of the previous line." (save-excursion (end-of-line) ;; sort tokens by line number (distance from current line) @@ -271,10 +287,10 @@ They have a line number of -1" (max (- (d2--decl-column previous-token) 4) 0)) (t (progn (message "uknown syntax %s" current-token) - (d2--decl-column current-token))) - )))) + (d2--decl-column current-token))))))) (defun d2-indent-line () + "This is the actual function called by Emacs to indent." (interactive) (indent-line-to (d2--calculate-desired-indentation))) From a9905aa30e635ca797a2994d1b444734baae5997 Mon Sep 17 00:00:00 2001 From: Mois Moshev Date: Fri, 5 Jul 2024 19:00:52 +0300 Subject: [PATCH 3/4] Fix emacs <30 compatibility of SORT --- d2-mode.el | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/d2-mode.el b/d2-mode.el index 77e9194..466644b 100644 --- a/d2-mode.el +++ b/d2-mode.el @@ -160,8 +160,9 @@ Use the newer token's line and column." (defun d2--sort-tokens-by-line (tokens) "Sort TOKENS by line number." (sort tokens - :key (lambda (token) - (d2--decl-line token)))) + (lambda (token1 token2) + (< (d2--decl-line token1) + (d2--decl-line token2))))) (defun d2--filter-not-found-tokens (tokens) "Filter TOKENS that were not detected. @@ -252,7 +253,7 @@ and the indentation of the previous line." (d2--parse-from-line))))))))) (current-token (car ordered-tokens)) (previous-token (cadr ordered-tokens))) - (message "tokens %s" ordered-tokens) + (message "tokens %s" (mapcar (lambda (t) (d2--decl-tag t)) ordered-tokens)) (cond ((and (d2--decl-tags-contain current-token 'node) (null previous-token)) 0) From 9f2e3c12aa477370d044c6d15074b491bddf1616 Mon Sep 17 00:00:00 2001 From: Mois Moshev Date: Fri, 5 Jul 2024 19:01:54 +0300 Subject: [PATCH 4/4] Fix invalid variable name --- d2-mode.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/d2-mode.el b/d2-mode.el index 466644b..8005a04 100644 --- a/d2-mode.el +++ b/d2-mode.el @@ -253,7 +253,7 @@ and the indentation of the previous line." (d2--parse-from-line))))))))) (current-token (car ordered-tokens)) (previous-token (cadr ordered-tokens))) - (message "tokens %s" (mapcar (lambda (t) (d2--decl-tag t)) ordered-tokens)) + (message "tokens %s" (mapcar (lambda (token) (d2--decl-tag token)) ordered-tokens)) (cond ((and (d2--decl-tags-contain current-token 'node) (null previous-token)) 0)