Skip to content

Commit

Permalink
Merge pull request #1 from andorsk/d2-mode-initial
Browse files Browse the repository at this point in the history
d2 initial commit
  • Loading branch information
andorsk authored Nov 22, 2022
2 parents b82ac7c + c73ba45 commit c9781fc
Show file tree
Hide file tree
Showing 2 changed files with 293 additions and 1 deletion.
87 changes: 86 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,87 @@
[![MELPA](https://melpa.org/packages/d2-mode-badge.svg)](https://melpa.org/#/d2-mode)

# d2-mode
d2 mode for emacs

A [d2](https://github.com/andorsk/d2-mode) extension for Emacs. This was heavily
inspired by [Mermaid Mode](https://github.com/abrochard/mermaid-mode).

**Status:** Work in Progress

## Why D2

Text to graph diagrams are awesome. I used Mermaid.js all the time and it was
fantastic, but there were a few things it couldn't do so I wanted to expand my
options.

Some things that I've noticed d2 has some interesting support in:

1. More themes
2. Code Blocks
3. More visual customization
4. More graph support
5. Autoformat

Learn more about [d2 here](https://d2lang.com/tour/intro/)

I also highly recommend mermaid for general graphing. I use d2 when I want to be
fancy, but mermaid in most other cases.

## Installation

1. Install from Melpa or load the d2-mode.el file
2. Install d2 binary from the d2 project if you plan to compile graphs in Emacs

## Usage

```text
C-c C-c - compile current file to an image
C-c C-f - compile given file to an image
C-c C-b - compile current buffer to an image
C-c C-r - compile current region to an image
C-c C-o - open in the live editor
C-c C-d - open the official doc
```

Note: All compile commands will open the output in a buffer to view the resulting image.

## Customization

### `d2` binary location

You can specify the location of `d2` with the variable `d2-location`, the default assumes you have the binary in your `PATH` (and for that you probably want/need to install [`d2`](https://github.com/andorsk/d2-mode)).

### Output format

By default `d2` will compile to svg format. You can change that by setting the variable `d2-output-format`.

### Temp directory

By default `d2-tmp-dir` points to `\tmp\`. Feel free to set it to a more appropriate location that works for you (e.g. on windows).

### Key bindings

To customize the key bindings but this into your `init.el` ...

```elisp
(setq d2-mode-map
(let ((map mermaid-mode-map))
(define-key map (kbd "C-c C-c") nil)
(define-key map (kbd "C-c C-f") nil)
(define-key map (kbd "C-c C-b") nil)
(define-key map (kbd "C-c C-r") nil)
(define-key map (kbd "C-c C-o") nil)
(define-key map (kbd "C-c C-d") nil)
(define-key map (kbd "C-c C-d c") 'd2-compile)
(define-key map (kbd "C-c C-d c") 'd2-compile)
(define-key map (kbd "C-c C-d f") 'd2-compile-file)
(define-key map (kbd "C-c C-d b") 'd2-compile-buffer)
(define-key map (kbd "C-c C-d r") 'd2-compile-region)
(define-key map (kbd "C-c C-d o") 'd2-open-browser)
(define-key map (kbd "C-c C-d d") 'd2-open-doc)
map))
```

## Bugs & Issues

Feel free to open an issue!
207 changes: 207 additions & 0 deletions d2-mode.el
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
;;; d2-mode.el --- major mode for working with d2 graphs -*- lexical-binding: t; -*-

;; Author: Andor Kesselman <[email protected]>
;; Copyright (C) 2022, Andor Kesselman
;;
;; 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 <http://www.gnu.org/licenses/>.

;; Version: 1.0
;; Author: Adrien Brochard
;; Keywords: d2 graphs tools processes
;; URL: https://github.com/andorsk/d2-mode
;; License: GNU General Public License >= 3
;; Package-Requires: ((f "0.20.0") (emacs "25.3"))

;;; Commentary:

;; Major mode for working with d2 graphs.
;; See https://github.com/terrastruct/d2

;;; Usage:

;; Currently supporting flow charts and sequence diagrams with syntax coloring and indentation.

;; C-c C-c to compile to an image
;; C-c C-f to compile file to an image
;; C-c C-r to compile region to an image
;; C-c C-b to compile buffer to an image
;; C-c C-o to open in the live editor
;; C-c C-d to open the official doc

;;; Customization:

;; You can specify the location of `d2` with the variable `d2-bin-location`,
;; the default assumes you have the binary in your exec PATH.

;; By default `d2` will compile to `png` format.
;; You can change that by setting the variable `d2-output-format`.

;; By default `d2` will use `/tmp` to store tmp-files.
;; You can change that by setting the variable `d2-tmp-dir`.

;; This code was inspired by mermaid-mode @
;; https://github.com/abrochard/mermaid-mode


;; STATE: Currently this is a work in progress
;;
;; Code

(require 'f)
(require 'browse-url)
(require 'ob)
(require 'ob-eval)

(defgroup d2-mode nil
"Major mode for working with d2 graphs."
:group 'extensions
:link '(url-link :tag "Repository" "https://github.com/andorsk/d2-mode"))

(defcustom d2-location "d2"
"d2 binary location"
:type 'string
:group 'd2-mode)

(defcustom d2-output-format ".svg"
"d2 output format."
:group 'd2-mode
:type 'string)

(defcustom d2-tmp-dir "/tmp/"
"Dir for tmp files."
:group 'd2-mode
:type 'string)

(defcustom d2-flags ""
"Additional flags to pass to the d2-cli."
:group 'd2-mode
:type 'string)

(defconst d2-font-lock-keywords
`((,(regexp-opt '("shape" "md" ) 'words) . font-lock-keyword-face)
("---\\|-?->*\\+?\\|==>\\|===|->" . font-lock-function-name-face)))

(defvar d2-syntax-table
(let ((syntax-table (make-syntax-table)))
;; Comment style "%% ..."
(modify-syntax-entry ?% ". 124" syntax-table)
(modify-syntax-entry ?\n ">" syntax-table)
syntax-table)
"Syntax table for `d2-mode'.")

(defvar org-babel-default-header-args:d2
'((:results . "file") (:exports . "results"))
"Default arguments for evaluating a d2 source block.")

(defun org-babel-execute:d2 (body params)
"Execute command with BODY and PARAMS from src block."
(let* ((out-file (or (cdr (assoc :file params))
(error "Mermaid requires a \":file\" header argument")))
(temp-file (org-babel-temp-file "d2-"))
(cmd (concat (shell-quote-argument d2-location)
" -o " (org-babel-process-file-name out-file)
" -i " temp-file
" " d2-flags)))
(with-temp-file temp-file (insert body))
(org-babel-eval cmd "")
nil))

(defun d2--locate-declaration (str)
"Locate a certain declaration and return the line difference and indentation.
STR is the declaration."
(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)))))

;; (defun d2-indent-line ()
;; "Indent the current line."
;; (interactive)
;; (save-excursion
;; (end-of-line)
;; (let ((graph (d2--locate-declaration "^graph\\|sequenceDiagram"))
;; (subgraph (d2--locate-declaration "subgraph \\|loop \\|alt \\|opt"))
;; (both (d2--locate-declaration "^graph \\|^sequenceDiagram$\\|subgraph \\|loop \\|alt \\|opt"))
;; (else (d2--locate-declaration "else "))
;; (end (d2--locate-declaration "^ *end *$")))
;; (indent-line-to
;; (cond ((equal (car graph) 0) 0) ;; this is a graph declaration
;; ((equal (car end) 0) (cdr subgraph)) ;; this is "end", indent to nearest subgraph
;; ((equal (car subgraph) 0) (+ 4 (cdr graph))) ;; this is a subgraph
;; ((equal (car else) 0) (cdr subgraph)) ;; this is "else:, indent to nearest alt
;; ;; everything else
;; ((< (car end) 0) (+ 4 (cdr both))) ;; no end in sight
;; ((< (car both) (car end)) (+ 4 (cdr both))) ;; (sub)graph declaration closer, +4
;; (t (cdr end)) ;; end declaration closer, same indent
;; )))))

(defun d2-compile ()
"Compile the current d2 file using d2."
(interactive)
(d2-compile-file (buffer-file-name)))

(defun d2-compile-buffer ()
"Compile the current d2 buffer using d2."
(interactive)
(let* ((tmp-file-name (concat d2-tmp-dir "current-buffer.d2")))
(write-region (point-min) (point-max) tmp-file-name)
(d2-compile-file tmp-file-name)))

(defun d2-compile-region ()
"Compile the current d2 region using d2."
(interactive)
(let* ((tmp-file-name (concat d2-tmp-dir "current-region.d2")))
(when (use-region-p)
(write-region (region-beginning) (region-end) tmp-file-name)
(d2-compile-file tmp-file-name))))

(defun d2-compile-file (file-name)
"Compile the given d2 file using d2."
(interactive "fFilename: ")
(let* ((input file-name)
(output (concat (file-name-sans-extension input) d2-output-format)))
(message output)
(apply #'call-process d2-location nil "*d2*" nil (list input output))
(display-buffer (find-file-noselect output t))))


(defun d2-open-doc ()
"Open the d2 home page and doc."
(interactive)
(browse-url "https://github.com/terrastruct/d2"))

(defvar d2-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "C-c C-c") 'd2-compile)
(define-key map (kbd "C-c C-f") 'd2-compile-file)
(define-key map (kbd "C-c C-b") 'd2-compile-buffer)
(define-key map (kbd "C-c C-r") 'd2-compile-region)
(define-key map (kbd "C-c C-o") 'd2-open-browser)
(define-key map (kbd "C-c C-d") 'd2-open-doc)
map))

;;;###autoload
(define-derived-mode d2-mode prog-mode "d2"
:syntax-table d2-syntax-table
(setq-local font-lock-defaults '(d2-font-lock-keywords))
(setq-local indent-line-function 'd2-indent-line)
(setq-local comment-start "%%")
(setq-local comment-end "")
(setq-local comment-start-skip "%%+ *"))

(provide 'd2-mode)

;;; d2-mode.el ends here

0 comments on commit c9781fc

Please sign in to comment.