Last active
July 18, 2017 00:24
-
-
Save jimblandy/30a38eb9b673979e6da8839089f79cbe to your computer and use it in GitHub Desktop.
Emacs major mode for editing SpiderMonkey JS_FN_HELP entries.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;;; edit-js-fn.el --- Edit JS_FN_HELP sections in SpiderMonkey shell files | |
;; See js-fn-edit-documentation. | |
(defvar js-fn-edit-original-buffer nil | |
"The buffer to which a js-fn-edit-mode buffer will store the edited text.") | |
(defvar js-fn-edit-original-region nil | |
"The region of js-fn-edit-original-buffer the original definition occupies.") | |
(defvar js-fn-edit-original-text nil | |
"The original text of the JS_FN_HELP form being edited.") | |
(defun js-fn-edit-documentation () | |
"Edit the documentation of a SpiderMonkey native function defined with JS_FN_HELP." | |
(interactive) | |
(let* ((original-buffer (current-buffer)) | |
(original-region (mapcar 'copy-marker (js-fn-bounds))) | |
(original-text (buffer-substring-no-properties | |
(car original-region) (cadr original-region))) | |
(buf (generate-new-buffer "*edit JS_FN_HELP*"))) | |
(set-buffer buf) | |
(erase-buffer) | |
(js-fn-edit-mode) | |
(setq js-fn-edit-original-buffer original-buffer) | |
(setq js-fn-edit-original-region original-region) | |
(setq js-fn-edit-original-text original-text) | |
(insert original-text) | |
(undo-boundary) | |
(goto-char (point-min)) | |
;; Unquote the summary line. | |
(forward-line 1) | |
(js-fn-unquote) | |
(js-fn-expect-and-delete | |
"," "JS_FN_HELP summary string should be followed by a comma") | |
;; Insert the separator line. | |
(insert "\n\n") | |
;; Unquote the detailed description. | |
(js-fn-unquote) | |
(js-fn-expect-and-delete | |
")," "JS_FN_HELP summary string should be followed by a closing paren and comma") | |
;; Remove common indentation from detailed description. | |
(goto-char (point-min)) | |
(forward-line 3) | |
(while (< (point) (point-max)) | |
(if (looking-at "^ ") (replace-match "")) | |
(forward-line 1)) | |
(goto-char (point-min)) | |
(forward-line 3) | |
(pop-to-buffer (current-buffer)) | |
(message "Press C-c C-c when you’re finished editing."))) | |
(defconst js-fn-edit-header-regex | |
"^ JS_FN_HELP(\".*\", .*, [0-9]+, [0-9]+," | |
"Regular expression for the first line of a well-formed JS_FN_HELP form.") | |
(defun js-fn-edit-finish () | |
"Replace the original JS_FN_HELP form with the edited version. | |
Once you’ve edited the buffer created by \\[js-fn-edit-documentation], | |
this command reformats your revised text as C++ code and replaces the original | |
JS_FN_HELP form with a new one." | |
(interactive) | |
(goto-char (point-min)) | |
;; Check that the buffer still seems well-formatted. | |
(unless (looking-at js-fn-edit-header-regex) | |
(message "JS_FN_HELP buffer needs to start with a valid JS_FN_HELP header")) | |
(forward-line 1) | |
(unless (looking-at ".") | |
(message "JS_FN_HELP buffer needs summary line")) | |
(forward-line 1) | |
(unless (and (bolp) (eolp)) | |
(message "JS_FN_HELP buffer needs a blank line between summary and full docs")) | |
(forward-line 1) | |
(unless (looking-at ".") | |
(message "JS_FN_HELP buffer needs some docs after summary and blank separator line")) | |
;; Remove the separator line. | |
(delete-region (1- (point)) (point)) | |
;; Indent the full docs. | |
(while (< (point) (point-max)) | |
(unless (and (bolp) (eolp)) | |
(insert " ")) | |
(forward-line 1)) | |
;; Remove any blank lines at the end, and ensure there is no final newline. | |
(goto-char (point-max)) | |
(skip-chars-backward " \t\n") | |
(delete-region (point) (point-max)) | |
;; Quote the summary. | |
(goto-char (point-min)) | |
(forward-line 1) | |
(js-fn-quote-region (point) (progn (end-of-line) (point))) | |
(insert ",") | |
;; Quote the detailed description. | |
(forward-line 1) | |
(js-fn-quote-region (point) (point-max)) | |
(insert "),\n") | |
;; Replace the original definition with our modified text. | |
(let ((new-text (buffer-string)) | |
(original-region js-fn-edit-original-region) | |
(original-text js-fn-edit-original-text)) | |
(set-buffer js-fn-edit-original-buffer) | |
(unless (string= (buffer-substring (car original-region) (cadr original-region)) | |
original-text) | |
(error "Original text in buffer ‘%s’ has changed; not updating" | |
(buffer-name (current-buffer)))) | |
(goto-char (car original-region)) | |
(delete-region (car original-region) (cadr original-region)) | |
(insert new-text)) | |
(quit-restore-window nil 'kill)) | |
(defun js-fn-bounds () | |
"Return the extent of the JS_FN_HELP form around point. | |
The result is a list (START END)." | |
(save-excursion | |
(forward-line 1) | |
(let ((limit (point))) | |
(unless (search-backward "JS_FN_HELP" nil t) | |
(error "Not within a JS_FN_HELP form")) | |
(unless (looking-at js-fn-edit-header-regex) | |
(message "JS_FN_HELP header doesn’t seem to be well-formed")) | |
(forward-line 0) | |
(let ((start (point))) | |
(forward-sexp 2) | |
(forward-line 1) | |
(list start (point)))))) | |
(defun js-fn-mark () | |
"Set the region around the JS_FN_HELP form around point." | |
(interactive) | |
(let ((bounds (js-fn-bounds))) | |
(set-mark (car bounds)) | |
(goto-char (cadr bounds)))) | |
(defun js-fn-unquote () | |
"Replace the C++ string starting at point with the text it represents. | |
There may be whitespace before the string literal. | |
If there are several string literals in succession that would be | |
concatenated into a single string, replace them all with the text they represent. | |
Leave point at the end of the text." | |
(with-syntax-table c++-mode-syntax-table | |
(let (start) | |
(while (progn | |
(setq start (point)) | |
(skip-chars-forward " \t\n") | |
(looking-at "\"")) | |
(let ((end (copy-marker (save-excursion (forward-sexp 1) (1- (point)))))) | |
(delete-region start (1+ (point))) | |
(while (search-forward "\\" end 'at-limit) | |
(cond | |
((looking-at "n") | |
(delete-region (1- (point)) (1+ (point))) | |
(insert-before-markers "\n")) | |
((looking-at "t") | |
(delete-region (1- (point)) (1+ (point))) | |
(insert-before-markers "\t")) | |
(t (delete-region (1- (point)) (point))))) | |
(delete-region (point) (1+ (point))))) | |
(goto-char start)))) | |
(defun js-fn-expect-and-delete (regexp message) | |
(skip-chars-forward " \t\n") | |
(unless (looking-at regexp) | |
(error message)) | |
(replace-match "")) | |
(defun js-fn-quote-region (start end) | |
"Turn the text from START to END into a properly quoted C string literal. | |
If the region contains newlines, quote them reasonably. | |
Leave point at the end of the text." | |
(save-restriction | |
(narrow-to-region start end) | |
(goto-char (point-min)) | |
(insert "\"") | |
(while (re-search-forward "[\n\"\\]" nil 'at-limit) | |
(if (string= (match-string 0) "\n") | |
(progn | |
(delete-region (1- (point)) (point)) | |
(insert "\\n") | |
(if (< (point) (point-max)) | |
(insert "\"\n\""))) | |
(forward-char -1) | |
(insert "\\") | |
(forward-char 1))) | |
(insert "\""))) | |
(defvar js-fn-edit-mode-map | |
(let ((map (make-sparse-keymap))) | |
(define-key map "\C-c\C-c" 'js-fn-edit-finish) | |
map) | |
"Local keymap for edit-js-fn-mode.") | |
(define-derived-mode js-fn-edit-mode | |
text-mode "JS_FN_HELP" | |
"A tiny major mode for editing SpiderMonkey JS_FN_HELP forms. | |
\\<js-fn-edit-mode-map>With point in a JS_FN_HELP form, run the command | |
\\[js-fn-edit-documentation]. This will pop up a buffer in | |
‘js-fn-edit-mode’ that holds the JS_FN_HELP form, but with all | |
quoting, escaping, and common indentation removed so you can edit | |
the text comfortably. | |
When you are done, press \\[js-fn-edit-finish] to replace the | |
original JS_FN_HELP form with the edited text." | |
(setq fill-column 72) | |
(make-local-variable 'js-fn-edit-original-buffer) | |
(make-local-variable 'js-fn-edit-original-region) | |
(make-local-variable 'js-fn-edit-original-text)) | |
(provide 'edit-js-fn) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment