;;; verilog-align-ports.el --- Align SystemVerilog port declarations -*- lexical-binding: t; -*- ;;; Commentary: ;; Align SystemVerilog ANSI-style port declarations in a contiguous block. ;;; Code: (require 'cl-lib) (require 'subr-x) (defun verilog-align-ports--line-port-p (pos) (save-excursion (goto-char pos) (let ((line (buffer-substring-no-properties (line-beginning-position) (line-end-position)))) (string-match-p "^\\s-*\\(input\\|output\\|inout\\)\\b" line)))) (defun verilog-align-ports--split-comment (line) (let ((pos (string-match "//" line))) (if pos (cons (substring line 0 pos) (substring line pos)) (cons line nil)))) (defun verilog-align-ports--parse-line (line) (let* ((split (verilog-align-ports--split-comment line)) (code (string-trim-right (car split))) (comment (cdr split))) (when (string-match "^\\(\\s-*\\)\\(input\\|output\\|inout\\)\\b\\s-*\\(.*\\)$" code) (let* ((indent (match-string 1 code)) (dir (match-string 2 code)) (rest (string-trim (match-string 3 code))) (name nil) (comma nil) (type "") (range "") (before "")) (if (string-match "\\(\\\\[^[:space:]]+\\|[A-Za-z_][A-Za-z0-9_$]*\\)\\(\\s-*,\\)?\\s-*$" rest) (progn (setq name (match-string 1 rest)) (setq comma (when (match-string 2 rest) ",")) (setq before (string-trim-right (substring rest 0 (match-beginning 1))))) (setq name rest) (setq comma nil) (setq before "")) (setq before (string-trim-right before)) (if (and (not (string-empty-p before)) (string-match "\\(\\[[^][]+\\]\\)\\s-*$" before)) (progn (setq range (match-string 1 before)) (setq type (string-trim-right (substring before 0 (match-beginning 1))))) (setq type (string-trim before)) (setq range "")) (list :indent indent :dir dir :type type :range range :name name :comma comma :comment comment))))) (defun verilog-align-ports--bounds () (save-excursion (and (verilog-align-ports--line-port-p (line-beginning-position)) (let ((start (line-beginning-position)) (end nil)) (while (and (not (bobp)) (save-excursion (forward-line -1) (verilog-align-ports--line-port-p (line-beginning-position)))) (forward-line -1) (setq start (line-beginning-position))) (goto-char start) (while (and (not (eobp)) (verilog-align-ports--line-port-p (line-beginning-position))) (forward-line 1)) (setq end (line-beginning-position)) (cons start end))))) (defun verilog-align-ports--collect (start end) (let (entries) (save-excursion (goto-char start) (while (< (point) end) (let* ((line (buffer-substring-no-properties (line-beginning-position) (line-end-position))) (entry (verilog-align-ports--parse-line line))) (when entry (push entry entries))) (forward-line 1))) (nreverse entries))) (defun verilog-align-ports--max-lengths (entries) (let ((max-dir 0) (max-type 0) (max-range 0) (max-name 0) (has-comment nil)) (dolist (entry entries) (setq max-dir (max max-dir (length (plist-get entry :dir)))) (setq max-type (max max-type (length (plist-get entry :type)))) (setq max-range (max max-range (length (plist-get entry :range)))) (let ((name (concat (plist-get entry :name) (or (plist-get entry :comma) "")))) (setq max-name (max max-name (length name)))) (when (plist-get entry :comment) (setq has-comment t))) (list max-dir max-type max-range max-name has-comment))) (defun verilog-align-ports--pad (max-len len) (make-string (+ 1 (- max-len len)) ?\s)) (defun verilog-align-ports--format-lines (entries) (let* ((base-indent (plist-get (car entries) :indent)) (maxes (verilog-align-ports--max-lengths entries)) (max-dir (nth 0 maxes)) (max-type (nth 1 maxes)) (max-range (nth 2 maxes)) (max-name (nth 3 maxes)) (has-comment (nth 4 maxes)) (has-type (> max-type 0)) (has-range (> max-range 0))) (mapcar (lambda (entry) (let* ((dir (plist-get entry :dir)) (type (plist-get entry :type)) (range (plist-get entry :range)) (name (concat (plist-get entry :name) (or (plist-get entry :comma) ""))) (comment (plist-get entry :comment)) (dir-pad (verilog-align-ports--pad max-dir (length dir))) (type-pad (when has-type (verilog-align-ports--pad max-type (length type)))) (range-pad (when has-range (verilog-align-ports--pad max-range (length range)))) (name-pad (when (and has-comment comment) (verilog-align-ports--pad max-name (length name))))) (concat base-indent dir dir-pad (when has-type type) (when has-type type-pad) (when has-range range) (when has-range range-pad) name (when (and has-comment comment) name-pad) (or comment "")))) entries))) (defun verilog-align-ports--apply (start lines) (save-excursion (goto-char start) (dolist (line lines) (delete-region (line-beginning-position) (line-end-position)) (insert line) (forward-line 1)))) ;;;###autoload (defun verilog-align-ports () "Align SystemVerilog port declarations around point." (interactive) (let ((bounds (verilog-align-ports--bounds))) (and bounds (let* ((start (car bounds)) (end (cdr bounds)) (entries (verilog-align-ports--collect start end))) (and entries (let ((lines (verilog-align-ports--format-lines entries))) (verilog-align-ports--apply start lines) t)))))) (provide 'verilog-align-ports) ;;; verilog-align-ports.el ends here