xwidget-plus-common.el 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. ;;; xwidget-plus-common.el -- Helper functions for xwidget-plus. -*- lexical-binding: t; -*-
  2. ;; Copyright (C) 2020 Damien Merenne <dam@cosinux.org>
  3. ;; This program is free software: you can redistribute it and/or modify
  4. ;; it under the terms of the GNU General Public License as published by
  5. ;; the Free Software Foundation, either version 3 of the License, or
  6. ;; (at your option) any later version.
  7. ;; This program is distributed in the hope that it will be useful,
  8. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. ;; GNU General Public License for more details.
  11. ;; You should have received a copy of the GNU General Public License
  12. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  13. ;;; Commentary:
  14. ;;
  15. ;;; Code:
  16. (defgroup xwidget-plus nil
  17. "Augment the xwidget webkit browser."
  18. :group 'convenience)
  19. (defcustom xwidget-plus-completion-system 'default
  20. "The completion system to be used by xwidget plus.
  21. Custom function should be a function that takes no arguments and
  22. returns an instance of an eieio class extending
  23. `xwidget-plus-completion-backend'."
  24. :group 'xwidget-plus
  25. :type '(radio
  26. (const :tag "Ido" ido)
  27. (const :tag "Helm" helm)
  28. (const :tag "Ivy" ivy)
  29. (const :tag "Default" default)
  30. (function :tag "Custom function")))
  31. (require 'json)
  32. (require 'subr-x)
  33. (require 'xwidget)
  34. (defun xwidget-plus-make-class (class style)
  35. "Generate a css CLASS definition from the STYLE alist."
  36. (format ".%s { %s }\\n" class (mapconcat (lambda (v) (format "%s: %s;" (car v) (cdr v))) style " ")))
  37. (defmacro --js (js _ &rest replacements)
  38. "Apply `format' on JS with REPLACEMENTS providing MMM mode delimiters.
  39. This file has basic support for javascript using MMM mode and
  40. local variables (see at the end of the file)."
  41. (declare (indent 2))
  42. `(format ,js ,@replacements))
  43. (defun xwidget-plus-js-string-escape (string)
  44. "Escape STRING for injection."
  45. (replace-regexp-in-string "\n" "\\\\n" (replace-regexp-in-string "'" "\\\\'" string)))
  46. (defun xwidget-plus-inject-head-element (xwidget tag id type content)
  47. "Insert TAG element under XWIDGET head with ID TYPE and CONTENT."
  48. (let* ((id (xwidget-plus-js-string-escape id))
  49. (tag (xwidget-plus-js-string-escape tag))
  50. (type (xwidget-plus-js-string-escape type))
  51. (content (xwidget-plus-js-string-escape content))
  52. (script (--js "
  53. __xwidget_id = '%s';
  54. if (!document.getElementById(__xwidget_id)) {
  55. var e = document.createElement('%s');
  56. e.type = '%s';
  57. e.id = __xwidget_id;
  58. e.innerHTML = '%s';
  59. document.getElementsByTagName('head')[0].appendChild(e);
  60. };
  61. null;
  62. " js-- id tag type content)))
  63. (xwidget-webkit-execute-script xwidget script)))
  64. (defun xwidget-plus-inject-script (xwidget id script)
  65. "Inject javascript SCRIPT in XWIDGET session using a script element with ID."
  66. (xwidget-plus-inject-head-element xwidget "script" id "text/javascript" script))
  67. (defun xwidget-plus-inject-style (xwidget id style)
  68. "Inject css STYLE in XWIDGET session using a style element with ID."
  69. (xwidget-plus-inject-head-element xwidget "style" id "text/css" style))
  70. (defun xwidget-plus-lisp-to-js (identifier)
  71. "Convert IDENTIFIER from Lisp style to javascript style."
  72. (replace-regexp-in-string "-" "_" (if (symbolp identifier) (symbol-name identifier) identifier)))
  73. (defvar xwidget-plus-js-scripts '() "An alist of list of javascript function.")
  74. (defun xwidget-plus-js-register-function (ns-name name js-script)
  75. "Register javascript function NAME in namespace NS-NAME with body JS-SCRIPT."
  76. (let* ((namespace (assoc ns-name xwidget-plus-js-scripts))
  77. (fun (when namespace (assoc name (cdr namespace)))))
  78. (cond (fun
  79. (delete fun namespace)
  80. (xwidget-plus-js-register-function ns-name name js-script))
  81. ((not namespace)
  82. (push (cons ns-name '()) xwidget-plus-js-scripts)
  83. (xwidget-plus-js-register-function ns-name name js-script))
  84. (t
  85. (push (cons name js-script) (cdr namespace))))
  86. (cons ns-name name)))
  87. (defun xwidget-plus-js-funcall (xwidget namespace name &rest arguments)
  88. "Invoke javascript FUNCTION in XWIDGET instance passing ARGUMENTS witch CALLBACK in NAMESPACE."
  89. ;;; Try to be smart
  90. (let* ((callback (car (last arguments)))
  91. (arguments (if (functionp callback) (reverse (cdr (reverse arguments))) arguments))
  92. (json-args (seq-map #'json-encode arguments))
  93. (arg-string (string-join json-args ", "))
  94. (namespace (xwidget-plus-lisp-to-js namespace))
  95. (name (xwidget-plus-lisp-to-js name))
  96. (script (format "__xwidget_plus_%s_%s(%s)" namespace name arg-string)))
  97. (xwidget-webkit-execute-script xwidget script (and (functionp callback) callback))))
  98. (defmacro xwidget-plus-js-def (namespace name arguments docstring js-body)
  99. "Create a function NAME with ARGUMENTS, DOCSTRING and JS-BODY.
  100. This will define a javascript function in the namespace NAMESPACE
  101. and a Lisp function to call it. "
  102. (declare (indent 3) (doc-string 4))
  103. (let* ((js-arguments (seq-map #'xwidget-plus-lisp-to-js arguments))
  104. (js-name (xwidget-plus-lisp-to-js name))
  105. (js-namespace (xwidget-plus-lisp-to-js namespace))
  106. (lisp-arguments (append '(xwidget) arguments '(&optional callback)))
  107. (script (--js "function __xwidget_plus_%s_%s(%s) {%s};" js--
  108. js-namespace js-name (string-join js-arguments ", ") (eval js-body)))
  109. (lisp-def `(defun ,(intern (format "xwidget-plus-%s-%s" namespace name)) ,lisp-arguments
  110. ,docstring
  111. (xwidget-plus-js-funcall xwidget (quote ,namespace) (quote ,name) ,@arguments callback)))
  112. (lisp-store `(xwidget-plus-js-register-function (quote ,namespace) (quote ,name) ,script)))
  113. `(progn ,lisp-def ,lisp-store)))
  114. (defun xwidget-plus-js-inject (xwidget ns-name)
  115. (let* ((namespace (assoc ns-name xwidget-plus-js-scripts))
  116. (script (mapconcat #'cdr (cdr namespace) "\n")))
  117. (xwidget-plus-inject-script xwidget (format "--xwidget-plus-%s" (symbol-name ns-name)) script)))
  118. ;; Local Variables:
  119. ;; eval: (mmm-mode)
  120. ;; eval: (mmm-add-group 'elisp-js '((elisp-rawjs :submode js-mode
  121. ;; :face mmm-code-submode-face
  122. ;; :delimiter-mode nil
  123. ;; :front "--js \"" :back "\" js--")
  124. ;; (elisp-defjs :submode js-mode
  125. ;; :face mmm-code-submode-face
  126. ;; :delimiter-mode nil
  127. ;; :front "xwidget-plus-defjs .*\n.*\"\"\n" :back "\")\n")))
  128. ;; mmm-classes: elisp-js
  129. ;; End:
  130. (provide 'xwidget-plus-common)
  131. ;;; xwidget-plus-common.el ends here