aboutsummaryrefslogtreecommitdiff
path: root/modules/prog-webdev.el
blob: 8832446acb979cc02765eab0748543840801d864 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
;;; prog-webdev.el --- Web Development Packages and Settings -*- lexical-binding: t; coding: utf-8; -*-
;; Author: Craig Jennings <c@cjennings.net>
;;
;;; Commentary:
;;
;; Layer: 3 (Domain Workflow).
;; Category: D/P.
;; Load shape: eager.
;; Eager reason: none necessary; currently eager but should load by web major
;;   modes (Phase 6 deferral candidate).
;; Top-level side effects: package configuration via use-package; warns at load
;;   if prettier is missing.
;; Runtime requires: system-lib.
;; Direct test load: yes.
;;
;; TypeScript, JavaScript, and HTML development with tree-sitter, LSP, and
;; prettier formatting.
;;
;; Installation:
;;   sudo pacman -S typescript-language-server typescript prettier
;;
;; Features:
;;   - Tree-sitter: Syntax highlighting for TS, TSX, JS (via treesit-auto)
;;   - LSP: Completion, go-to-definition, hover docs, rename, references
;;   - Formatting: prettier via C-; f
;;   - Web-mode: Mixed HTML/CSS/JS templates with context-aware completion
;;
;; Workflow:
;;   1. Open .ts/.tsx/.js file → tree-sitter mode + LSP auto-starts
;;   2. C-; f → Format with prettier
;;   3. Open .html file → web-mode with LSP support

;;; Code:

(require 'system-lib)  ; for cj/executable-find-or-warn

(defvar typescript-ts-mode-map)
(defvar tsx-ts-mode-map)
(defvar js-ts-mode-map)

;; Forward declarations for LSP
(declare-function lsp-deferred "lsp-mode")

;; Forward declarations for external packages
(declare-function company-mode "company")

(defvar ts-language-server-path "typescript-language-server"
  "Path to typescript-language-server executable.
Install with: sudo pacman -S typescript-language-server")

(defvar prettier-path "prettier"
  "Path to prettier executable.
Install with: sudo pacman -S prettier")

;; Warn at load time if prettier is missing rather than waiting for the
;; first format-on-save to fail mid-edit.
(cj/executable-find-or-warn prettier-path "prettier formatter" 'prog-webdev)

;; ------------------------------ Web Dev Setup --------------------------------
;; shared setup for TypeScript, JavaScript, and TSX modes

(defun cj/webdev-setup ()
  "Set up common preferences for web development buffers."
  (company-mode)
  (flyspell-prog-mode)
  (superword-mode)
  (setq-local fill-column 100)
  (setq-local tab-width 2)
  (setq-local standard-indent 2)
  (setq-local indent-tabs-mode nil)
  (electric-pair-local-mode t)

  ;; Enable LSP if available
  (when (and (fboundp 'lsp-deferred)
             (executable-find ts-language-server-path))
    (lsp-deferred)))

(defun cj/--webdev-format-args (file)
  "Return the prettier argv list that formats FILE's contents on stdin.
No shell quoting is needed: the args are passed to prettier directly
via `call-process-region', so FILE can contain spaces or shell
metacharacters without risk."
  (list "--stdin-filepath" file))

(defun cj/--webdev-format-region (program &rest args)
  "Replace the buffer with PROGRAM ARGS run over its contents, via argv.
Runs PROGRAM (with ARGS) on the whole buffer through
`call-process-region' — no shell, so no quoting or word-splitting.
The buffer is replaced only when PROGRAM exits zero; on a non-zero
exit the buffer is left untouched and an error is signalled with
the program's stderr text.  Point is preserved as closely as the
reformatted size allows.  Returns t on success."
  (let* ((point (point))
         (src (current-buffer))
         (out (generate-new-buffer " *webdev-format-out*"))
         (status (apply #'call-process-region
                        (point-min) (point-max) program
                        nil out nil args)))
    (unwind-protect
        (if (and (integerp status) (zerop status))
            (progn
              (with-current-buffer src
                (replace-buffer-contents out)
                (goto-char (min point (point-max))))
              t)
          (user-error "%s failed: %s" program
                      (string-trim (with-current-buffer out (buffer-string)))))
      (kill-buffer out))))

(defun cj/webdev-format-buffer ()
  "Format the current buffer with prettier.
Detects the file type automatically from the filename."
  (interactive)
  (if (executable-find prettier-path)
      (apply #'cj/--webdev-format-region prettier-path
             (cj/--webdev-format-args (or buffer-file-name "file.ts")))
    (user-error "prettier not found; install with: sudo pacman -S prettier")))

(defun cj/webdev-keybindings ()
  "Set up keybindings for web development buffers."
  (local-set-key (kbd "C-; f") #'cj/webdev-format-buffer))

;; ----------------------------- TypeScript / JS -------------------------------
;; tree-sitter modes (built-in, Emacs 29+)
;; NOTE: No :mode directives — treesit-auto (in prog-general.el) handles
;; the auto-mode-alist mappings and auto-installs grammars on first use.

(use-package typescript-ts-mode
  :ensure nil
  :defer t
  :hook
  ((typescript-ts-mode . cj/webdev-setup)
   (typescript-ts-mode . cj/webdev-keybindings)))

(use-package tsx-ts-mode
  :ensure nil
  :defer t
  :hook
  ((tsx-ts-mode . cj/webdev-setup)
   (tsx-ts-mode . cj/webdev-keybindings)))

(use-package js-ts-mode
  :ensure nil
  :defer t
  :hook
  ((js-ts-mode . cj/webdev-setup)
   (js-ts-mode . cj/webdev-keybindings)))

;; ----------------------------------- LSP -------------------------------------
;; TypeScript/JavaScript LSP configuration
;; Core LSP setup is in prog-general.el

(use-package lsp-mode
  :hook ((typescript-ts-mode tsx-ts-mode js-ts-mode) . lsp-deferred))

;; --------------------------------- CSS Eldoc ---------------------------------
;; CSS info in the echo area

(use-package css-eldoc
  :defer t)

;; ---------------------------------- Web Mode ---------------------------------
;; major mode for editing web templates (HTML with embedded JS/CSS)

(use-package web-mode
  :defer t
  :custom
  (web-mode-enable-current-element-highlight t)
  (web-mode-markup-indent-offset 2)
  (web-mode-code-indent-offset 2)
  (web-mode-engines-alist '(("django" . "\\.html\\'")))
  :mode ("\\.html?$" . web-mode)
  :hook (web-mode . cj/webdev-keybindings))

(provide 'prog-webdev)
;;; prog-webdev.el ends here.