-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrivet-mode.el
234 lines (186 loc) · 8.44 KB
/
rivet-mode.el
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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
;;; rivet-mode.el --- A minor mode for editing Apache Rivet files -*- lexical-binding: t; -*-
;;
;; Author: Jade Michael Thornton
;; Copyright (c) 2019-2020 Jade Michael Thornton
;; Package-Requires: ((emacs "24") (web-mode "16"))
;; URL: https://gitlab.com/thornjad/rivet-mode
;; Version: 4.1.0
;;
;; This file is not part of GNU Emacs
;;; Commentary:
;;
;; [![MELPA: rivet-mode](https://melpa.org/packages/rivet-mode-badge.svg)](https://melpa.org/#/rivet-mode) [![ISC License](https://img.shields.io/badge/license-ISC-green.svg)](./LICENSE) [![](https://img.shields.io/github/languages/code-size/thornjad/rivet-mode.svg)](https://gitlab.com/thornjad/rivet-mode) [![](https://img.shields.io/github/v/tag/thornjad/rivet-mode.svg?label=version&color=yellowgreen)](https://gitlab.com/thornjad/rivet-mode/-/tags)
;;
;; Rivet mode is a minor mode for editing Apache Rivet files. It automatically
;; detects whether TCL or HTML is currently being edited and uses the major
;; modes tcl-mode and web-mode, respectively.
;;
;; By default, `rivet-mode' requires `tcl' (built-in) and `web-mode'. To use
;; another mode, customize `rivet-mode-host-mode' and `rivet-mode-inner-mode' to
;; suit.
;; Installation:
;;
;; Install the `rivet-mode' package from MELPA.
;; Usage:
;;
;; The mode will be activated upon opening a Rivet file. There are a handful of
;; convenient function available inside this file. It is recommended to bind
;; these to keys of your choosing.
;;
;; Provided functions:
;;
;; - `rivet-insert-tcl': Insert TCL tags ("<?" and "?>") at point, and move point
;; inside of the tags. This will also switch to TCL mode.
;;
;; - `rivet-insert-begin-tcl': Insert an opening TCL tag at point and move inside
;; it.
;;
;; - `rivet-insert-end-tcl': Insert a closing TCL tag at point and move outside
;; it. This will also switch to web mode.
;;
;; - `rivet-insert-echo': Insert TCL echo tags ("<?=" and "?>") at point and move
;; inside them. This will also switch to TCL mode.
;;
;; - `rivet-insert-begin-echo': Insert an opening TCL echo tag at point and move
;; inside it.
;; Customization:
;;
;; The variable `rivet-mode-host-mode' determines the "host" major mode, which
;; is `web-mode' by default.
;;
;; The variable `rivet-mode-inner-mode' determines the "inner" major mode, which
;; is the built-in `tcl-mode' by default.
;;
;; The variable `rivet-mode-delimiters' defines the left and right delimiters
;; which demark the bounds of the "inner" major mode (TCL). These are "<?" and
;; "?>" by default. Note that the "<?=" delimiter, which marks the start of an
;; expression, still begins with "<?" and so will be caught.
;;; License:
;;
;; Copyright (c) 2019-2020 Jade Michael Thornton
;;
;; Permission to use, copy, modify, and/or distribute this software for any
;; purpose with or without fee is hereby granted, provided that the above
;; copyright notice and this permission notice appear in all copies.
;;
;; The software is provided "as is" and the author disclaims all warranties with
;; regard to this software including all implied warranties of merchantability
;; and fitness. In no event shall the author be liable for any special, direct,
;; indirect, or consequential damages or any damages whatsoever resulting from
;; loss of use, data or profits, whether in an action of contract, negligence or
;; other tortious action, arising out of or in connection with the use or
;; performance of this software.
;;; Code:
;;; Variables
(defvar rivet-mode-host-mode '("Web" web-mode web-mode)
"The host mode is the 'outer' mode, i.e. HTML, CSS and JS.
Format is '(NAME MAJOR-MODE PACKAGE).
The PACKAGE part of the list is the name of the package which provides
MAJOR-MODE.")
(defvar rivet-mode-inner-mode '("TCL" tcl-mode tcl)
"The inner mode is contained within the `rivet-mode-delimiters', used for TCL.
Format is '(NAME MAJOR-MODE PACKAGE). See `rivet-mode-delimiters' for more on
the demarcation between the inner and host modes.
The PACKAGE part of the list is the name of the package which provides
MAJOR-MODE.")
(defvar rivet-mode-delimiters '("<?" "?>")
"These delimiters denote the boundaries of the 'inner' mode, i.e. TCL.
The car and cadr are the left and right delimiters. That is to say the format is
'(LEFT-DELIMITER RIGHT-DELIMITER). Note that the '<?=' output syntax is included
since it begins with '<?'.")
(make-variable-buffer-local
(defvar rivet-mode--last-position 0
"Value of point from the last time an update was attempted.
This buffer-local variable allows `rivet-mode' to tell if the point has moved,
and if, therefore, the current mode should be re-evaluated. This variable should
not be changed manually."))
(defvar rivet-mode-hook nil
"*Hook called upon running minor mode function `rivet-mode'.")
(defvar rivet-mode-change-hook nil
"*Hook called upon changing between inner and host modes.")
;;; The parts that do the real work
(defun rivet-mode--change-mode (to-mode)
"Call TO-MODE, then set up the hook again and run rivet-mode-change-hook."
;; call our new mode function
(funcall (cadr to-mode))
;; HACK this is crappy, but for some reason that funcall removes us from the
;; post-command hook, so let's put us back in.
(add-hook 'post-command-hook #'rivet-mode--maybe-change-mode nil t)
;; After the mode was set, we reread the "Local Variables" section.
(hack-local-variables)
(if rivet-mode-change-hook (run-hooks 'rivet-mode-change-hook)))
(defun rivet-mode--maybe-change-mode ()
"Change switch between inner and host modes if appropriate.
If there is no active region and point has changed, then determine if point is
in a host or inner section. If point has moved to a different section, change to
that section's major mode."
(when (and (not (region-active-p))
(not (equal (point) rivet-mode--last-position)))
;; cache our position for the next call
(setq rivet-mode--last-position (point))
(let ((last-left-delim -1) (last-right-delim -1))
(save-excursion
(if (search-backward (car rivet-mode-delimiters) nil t)
(setq last-left-delim (point))))
(save-excursion
(if (search-backward (cadr rivet-mode-delimiters) nil t)
(setq last-right-delim (point))))
(let ((section-mode
(if (and (not (and (= last-left-delim -1)
(= last-right-delim -1)))
(>= last-left-delim last-right-delim))
rivet-mode-inner-mode
rivet-mode-host-mode)))
(unless (equal major-mode (cadr section-mode))
(rivet-mode--change-mode section-mode))))))
(defmacro rivet-mode--insert-move (str pos)
`(progn (save-excursion (insert ,str)) (forward-char ,pos)))
;;; Public functions
(defun rivet-insert-tcl ()
"Insert TCL tags <? and ?> at point."
(interactive)
(rivet-mode--insert-move "<? ?>" 3))
(defun rivet-insert-begin-tcl ()
"Insert an opening TCL tag at point."
(interactive)
(rivet-mode--insert-move "<? " 3))
(defun rivet-insert-end-tcl ()
"Insert a closing TCL tag at point."
(interactive)
(rivet-mode--insert-move "?> " 3))
(defun rivet-insert-echo ()
"Insert TCL echo tags at point."
(interactive)
(rivet-mode--insert-move "<?= ?>" 4))
(defun rivet-insert-begin-echo ()
"Insert an opening TCL echo tag at point."
(interactive)
(rivet-mode--insert-move "<?= " 4))
;;; Minor mode and auto-mode setup
;;;###autoload
(define-minor-mode rivet-mode
"Minor mode for editing Apache Rivet files.
Rivet mode intelligently switches between TCL and Web major modes for editing
Rivet files."
:lighter " Rivet"
;; Load the required packages
(dolist (mode (list rivet-mode-host-mode rivet-mode-inner-mode))
(unless (require (caddr mode) nil t)
(error
"Rivet mode requires %s to work properly. Please ensure this package is available"
(symbol-name (caddr mode)))))
;; Chances are we are at position 1 because the file has just been opened
;; cold. Since the inner mode requires delimiters and we could not possibly be
;; within a delimiter at position 1 (because the delimiters are at least two
;; characters), we must be in the host mode. If, however, we are not at
;; position 1, we need to check.
(if (eql (point) 1)
(progn
(funcall (cadr rivet-mode-host-mode))
(add-hook 'post-command-hook #'rivet-mode--maybe-change-mode nil t))
(rivet-mode--maybe-change-mode))
(if rivet-mode-hook (run-hooks 'rivet-mode-hook)))
;;;###autoload
(add-to-list 'auto-mode-alist '("\\.rvt\\'" . rivet-mode))
(provide 'rivet-mode)
;;; rivet-mode.el ends here