Personal emacs config
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

165 lines
5.9 KiB

  1. ;;; skewer-html.el --- skewer support for live-interaction HTML -*- lexical-binding: t; -*-
  2. ;; This is free and unencumbered software released into the public domain.
  3. ;;; Commentary:
  4. ;; This minor mode provides functionality for HTML like plain Skewer
  5. ;; does for JavaScript. There's no clean way to replace the body and
  6. ;; head elements of a live document, so "evaluating" these elements is
  7. ;; not supported.
  8. ;; * C-M-x -- `skewer-html-eval-tag'
  9. ;; See also `skewer-html-fetch-selector-into-buffer' for grabbing the
  10. ;; page as it current exists.
  11. ;;; Code:
  12. (require 'cl-lib)
  13. (require 'sgml-mode)
  14. (require 'skewer-mode)
  15. ;; Macros
  16. (defmacro skewer-html--with-html-mode (&rest body)
  17. "Evaluate BODY as if in `html-mode', using a temp buffer if necessary."
  18. (declare (indent 0))
  19. (let ((orig-buffer (make-symbol "orig-buffer"))
  20. (temp-buffer (make-symbol "temp-buffer"))
  21. (orig-point (make-symbol "orig-point")))
  22. `(let ((,temp-buffer (and (not (eq major-mode 'html-mode))
  23. (generate-new-buffer " *skewer-html*")))
  24. (,orig-buffer (current-buffer))
  25. (,orig-point (point)))
  26. (unwind-protect
  27. (with-current-buffer (or ,temp-buffer ,orig-buffer)
  28. (when ,temp-buffer
  29. (insert-buffer-substring ,orig-buffer)
  30. (setf (point) ,orig-point)
  31. (html-mode))
  32. ,@body)
  33. (when ,temp-buffer
  34. (kill-buffer ,temp-buffer))))))
  35. ;; Selector computation
  36. (defun skewer-html--cleanup (tag)
  37. "Cleanup TAG name from sgml-mode."
  38. (skewer-html--with-html-mode
  39. (replace-regexp-in-string "/$" "" (sgml-tag-name tag))))
  40. (defun skewer-html--tag-after-point ()
  41. "Return the tag struct for the tag immediately following point."
  42. (skewer-html--with-html-mode
  43. (save-excursion
  44. (forward-char 1)
  45. (sgml-parse-tag-backward))))
  46. (defun skewer-html--get-context ()
  47. "Like `sgml-get-context' but to the root, skipping close tags."
  48. (skewer-html--with-html-mode
  49. (save-excursion
  50. (cl-loop for context = (sgml-get-context)
  51. while context
  52. nconc (nreverse context) into tags
  53. finally return (cl-delete 'close tags :key #'sgml-tag-type)))))
  54. (cl-defun skewer-html-compute-tag-nth (&optional (point (point)))
  55. "Compute the position of this tag within its parent."
  56. (skewer-html--with-html-mode
  57. (save-excursion
  58. (setf (point) point)
  59. (let ((context (skewer-html--get-context)))
  60. (when context
  61. (let ((tag-name (skewer-html--cleanup (car context)))
  62. (target-depth (1- (length context))))
  63. (cl-loop with n = 0
  64. ;; If point doesn't move, we're at the root.
  65. for point-start = (point)
  66. do (sgml-skip-tag-backward 1)
  67. until (= (point) point-start)
  68. ;; If depth changed, we're done.
  69. for current-depth = (length (skewer-html--get-context))
  70. until (< current-depth target-depth)
  71. ;; Examine the sibling tag.
  72. for current-name = (save-excursion
  73. (forward-char)
  74. (sgml-parse-tag-name))
  75. when (equal current-name tag-name)
  76. do (cl-incf n)
  77. finally return n)))))))
  78. (defun skewer-html-compute-tag-ancestry ()
  79. "Compute the ancestry chain at point."
  80. (skewer-html--with-html-mode
  81. (nreverse
  82. (cl-loop for tag in (skewer-html--get-context)
  83. for nth = (skewer-html-compute-tag-nth (1+ (sgml-tag-start tag)))
  84. for name = (skewer-html--cleanup tag)
  85. unless (equal name "html")
  86. collect (list name nth)))))
  87. (defun skewer-html-compute-selector ()
  88. "Compute the selector for exactly the tag around point."
  89. (let ((ancestry (skewer-html-compute-tag-ancestry)))
  90. (mapconcat (lambda (tag)
  91. (format "%s:nth-of-type(%d)" (cl-first tag) (cl-second tag)))
  92. ancestry " > ")))
  93. ;; Fetching
  94. (defun skewer-html-fetch-selector (selector)
  95. "Fetch the innerHTML of a selector."
  96. (let ((result (skewer-eval-synchronously selector :type "fetchselector")))
  97. (if (skewer-success-p result)
  98. (cdr (assoc 'value result))
  99. "")))
  100. (defun skewer-html-fetch-selector-into-buffer (selector)
  101. "Fetch the innerHTML of a selector and insert it into the active buffer."
  102. (interactive "sSelector: ")
  103. (insert (skewer-html-fetch-selector selector)))
  104. ;; Evaluation
  105. (defun skewer-html-eval (string ancestry &optional append)
  106. "Load HTML into a selector, optionally appending."
  107. (let ((ancestry* (cl-coerce ancestry 'vector))) ; for JSON
  108. (skewer-eval string nil :type "html" :extra `((ancestry . ,ancestry*)
  109. (append . ,append)))))
  110. (defun skewer-html-eval-tag ()
  111. "Load HTML from the immediately surrounding tag."
  112. (interactive)
  113. (let ((ancestry (skewer-html-compute-tag-ancestry)))
  114. (save-excursion
  115. ;; Move to beginning of opening tag
  116. (let* ((beg (skewer-html--with-html-mode
  117. (sgml-skip-tag-forward 1) (point)))
  118. (end (skewer-html--with-html-mode
  119. (sgml-skip-tag-backward 1) (point)))
  120. (region (buffer-substring-no-properties beg end)))
  121. (skewer-flash-region beg end)
  122. (if (= (length ancestry) 1)
  123. (error "Error: cannot eval body and head tags.")
  124. (skewer-html-eval region ancestry nil))))))
  125. ;; Minor mode definition
  126. (defvar skewer-html-mode-map
  127. (let ((map (make-sparse-keymap)))
  128. (prog1 map
  129. (define-key map (kbd "C-M-x") 'skewer-html-eval-tag)))
  130. "Keymap for skewer-html-mode")
  131. ;;;###autoload
  132. (define-minor-mode skewer-html-mode
  133. "Minor mode for interactively loading new HTML."
  134. :lighter " skewer-html"
  135. :keymap skewer-html-mode-map
  136. :group 'skewer)
  137. (provide 'skewer-html)
  138. ;;; skewer-html.el ends here