Browse Source

Rename to xwwp and split packages.

Damien Merenne 6 years ago
parent
commit
b3b7b904c3
12 changed files with 619 additions and 550 deletions
  1. 2 2
      Cask
  2. 5 2
      Makefile
  3. 11 11
      README.md
  4. 14 14
      test/test-helper.el
  5. 90 84
      test/xwwp-follow-link-test.el
  6. 0 324
      xwidget-plus-follow-link.el
  7. 0 55
      xwidget-plus.el
  8. 70 0
      xwwp-follow-link-helm.el
  9. 56 0
      xwwp-follow-link-ido.el
  10. 53 0
      xwwp-follow-link-ivy.el
  11. 267 0
      xwwp-follow-link.el
  12. 51 58
      xwwp.el

+ 2 - 2
Cask

@@ -1,8 +1,8 @@
 (source gnu)
 (source melpa)
 
-(package "xwidget-plus" "0.1" "xwidget-webkit enhancement.")
-(package-file "xwidget-plus.el")
+(package "xwwp" "0.1" "xwidget-webkit enhancement.")
+(package-file "xwwp.el")
 
 (development
  (depends-on "ert-runner"

+ 5 - 2
Makefile

@@ -16,7 +16,7 @@ TESTS        = $(wildcard test/*.el)
 TAR          = $(DIST)/__PROJECT-NAME__-$(VERSION).tar
 
 
-.PHONY: all check test unit lint install uninstall reinstall clean-all clean clean-elc
+.PHONY: all check test unit lint install uninstall reinstall clean-all clean clean-elc compile package-lint
 
 all : $(PKG_DIR) $(TAR)
 
@@ -56,12 +56,15 @@ test: unit
 unit: $(PKG_DIR)
 	${CASK} exec ert-runner --win
 
-lint : $(SRCS) clean-elc
+lint : compile package-lint
+
+compile: ${SRCS} clean-elc
 	# Byte compile all and stop on any warning or error
 	${CASK} emacs $(EMACSFLAGS) \
 	--eval "(setq byte-compile-error-on-warn t)" \
 	-L . -f batch-byte-compile ${SRCS}
 
+package-lint: ${SRCS}
 	# Run package-lint to check for packaging mistakes
 	${CASK} emacs $(EMACSFLAGS) \
 	-l package-lint.el \

+ 11 - 11
README.md

@@ -3,11 +3,11 @@
 This package enhance the integrated xwidget-webkit browser with hopefully useful
 functionnalities.
 
-![CI](https://github.com/canatella/xwidget-plus/workflows/CI/badge.svg?branch=master)
+![CI](https://github.com/canatella/xwwp/workflows/CI/badge.svg?branch=master)
 
 ## Follow link
 
-Using `xwidget-plus-follow-link` to choose a link on the current web
+Using `xwwp-follow-link` to choose a link on the current web
 page. It also highlight the candidates on the web page.
 
 ![Imgur](https://i.imgur.com/1KO70FE.gif)
@@ -18,19 +18,19 @@ with your backend of choice and I'll have a look at what can be done. Or better,
 fork and create a pull request, most of the needed code is already there, it
 just needs to be hooked.
 
-## Switch to xwidget on browse
-
-This package provides the `xwiget-plus-browse-url` function. Unlike
-`xwidget-webkit-browse-url`, when reusing an existing session, it will bring it
-to the front.
-
 ## How to install
 
 Sorry, no melpa as of now. Should be added quite soon.
 
 ```
-(use-package xwidget-plus
-  :load-path "~/.emacs.d/xwidget-plus"
+(use-package xwwp-follow-link
+  :load-path "~/.emacs.d/xwwp-follow-link"
   :bind (:map xwidget-webkit-mode-map
-              ("v" . xwidget-plus-follow-link)))
+              ("v" . xwwp-follow-link)))
 ```
+
+## Development
+
+The goal of this package is to enhance the `xwidget-webkit` browser. If you have
+any code or feature suggestion that you think should make it into this package,
+please open an issue or better, create a pull request!

+ 14 - 14
test/test-helper.el

@@ -27,22 +27,22 @@
     (insert (backtrace-to-string frames))))
 
 
-(defconst xwidget-plus-test-path (file-name-as-directory
+(defconst xwwp-test-path (file-name-as-directory
                              (file-name-directory (or load-file-name buffer-file-name)))
   "The test directory.")
 
-(defconst xwidget-plus-test-data-path (file-name-as-directory
-                                       (concat xwidget-plus-test-path "data"))
+(defconst xwwp-test-data-path (file-name-as-directory
+                                       (concat xwwp-test-path "data"))
   "The test data directory.")
 
-(defconst xwidget-plus-root-path (file-name-as-directory
+(defconst xwwp-root-path (file-name-as-directory
                                          (file-name-directory
-                                          (directory-file-name xwidget-plus-test-path)))
+                                          (directory-file-name xwwp-test-path)))
   "The package root path.")
 
-(add-to-list 'load-path xwidget-plus-root-path)
+(add-to-list 'load-path xwwp-root-path)
 
-(defun xwidget-plus-event-dispatch (&optional seconds)
+(defun xwwp-event-dispatch (&optional seconds)
   (save-excursion
     (with-current-buffer (xwidget-buffer (xwidget-webkit-last-session))
       (let ((event (read-event nil nil seconds)))
@@ -51,26 +51,26 @@
           (xwidget-event-handler))
         event))))
 
-(defun xwidget-plus-event-loop ()
+(defun xwwp-event-loop ()
   (save-excursion
     (with-current-buffer (xwidget-buffer (xwidget-webkit-last-session))
-      (while (xwidget-plus-event-dispatch 0.3)))))
+      (while (xwwp-event-dispatch 0.3)))))
 
 (defmacro with-browse (file &rest body)
   (declare (indent 1))
-  (let ((url (format "file://%s%s" (expand-file-name xwidget-plus-test-data-path) file)))
+  (let ((url (format "file://%s%s" (expand-file-name xwwp-test-data-path) file)))
     `(progn
        (xwidget-webkit-browse-url ,url)
        ;; this will trigger a loading event
-       (xwidget-plus-event-dispatch)
+       (xwwp-event-dispatch)
        (let ((xwidget (xwidget-webkit-last-session)))
-         (xwidget-plus-js-inject xwidget 'test)
-         (xwidget-plus-event-loop)
+         (xwwp-js-inject xwidget 'test)
+         (xwwp-event-loop)
          (with-current-buffer (xwidget-buffer xwidget)
            ,@body)))))
 
 
-(defun xwidget-plus-wait-for (script)
+(defun xwwp-wait-for (script)
   "Wait until scripts evaluate to true")
 (provide 'test-helper)
 ;;; test-helper.el ends here

+ 90 - 84
test/xwidget-plus-follow-link-test.el → test/xwwp-follow-link-test.el

@@ -1,4 +1,4 @@
-;;; xwidget-plus-follow-link-test.el -- xwwp follow link test suite -*- lexical-binding: t; -*-
+;;; xwwp-follow-link-test.el -- xwwp follow link test suite -*- lexical-binding: t; -*-
 
 ;; Copyright (C) 2020 Damien Merenne <dam@cosinux.org>
 
@@ -21,14 +21,18 @@
 
 ;;; Code:
 
+(require 'cl-lib)
 (require 'test-helper)
 (require 'with-simulated-input)
-(require 'xwidget-plus-follow-link)
+(require 'xwwp-follow-link)
+(require 'xwwp-follow-link-ido)
+(require 'xwwp-follow-link-ivy)
+(require 'xwwp-follow-link-helm)
 
 (setq completing-read-function #'completing-read-default)
 
 ;; Some usefull javascript
-(xwidget-plus-js-def test element-classes (selector)
+(xwwp-js-def test element-classes (selector)
   "Fetch the list of css class for element matching SELECTOR.""
 map = Array.prototype.map;
 r = {};
@@ -40,70 +44,71 @@ document.querySelectorAll(selector).forEach(l => {
 return r;
 ")
 
-(xwidget-plus-js-def test current-location ()
+(xwwp-js-def test current-location ()
   "Fetch the current url.""
 return '' + window.location;
 ")
 
 ;; A mocked backend class that doesn't interactively read anything.
-(defclass xwidget-plus-completion-backend-test (xwidget-plus-completion-backend)
+(defclass xwwp-follow-link-completion-backend-test (xwwp-follow-link-completion-backend)
   ((candidates-mock :initarg :candidates-mock)
    (selected-mock :initarg :selected-mock)
    (action-fn)
    (classes)
    (location)))
 
-(defun xwidget-plus-update-fn-callback (result)
+(defun xwwp-update-fn-callback (result)
   "Called after updating candidates with the css classes in RESULT."
-  (let ((backend xwidget-plus-follow-link-completion-backend-instance))
+  (let ((backend xwwp-follow-link-completion-backend-instance))
     ;; Store the results.
     (oset backend classes (seq-map #'identity result))
     ;; Trigger the action function with the mocked selected link
     (funcall (oref backend action-fn) (oref backend selected-mock))
-    (xwidget-plus-event-dispatch)))
+    (xwwp-event-dispatch)))
 
-(defun xwidget-plus-location-callback (result)
+(defun xwwp-location-callback (result)
   "Called after updating candidates with the css classes in RESULT."
-  (let ((backend xwidget-plus-follow-link-completion-backend-instance))
+  (let ((backend xwwp-follow-link-completion-backend-instance))
     ;; Store the results.
     (oset backend location result)))
 
-(cl-defmethod xwidget-plus-follow-link-candidates ((backend xwidget-plus-completion-backend-test))
+(cl-defmethod xwwp-follow-link-candidates ((backend xwwp-follow-link-completion-backend-test))
   "Return the list of BACKEND mocked candidates."
   (oref backend candidates-mock))
 
-(cl-defmethod xwidget-plus-follow-link-read ((backend xwidget-plus-completion-backend-test) _ _ action-fn update-fn)
+(cl-defmethod xwwp-follow-link-read ((backend xwwp-follow-link-completion-backend-test) _ _ action-fn update-fn)
   "Store ACTION-FN in BACKEND, call the UPDATE-FN, and fetch the link element classes."
   ;; Stoire action so that we can call it after having fetch the css classes.
   (oset backend action-fn action-fn)
   ;; Trigger the javascript update.
   (funcall update-fn)
   ;; Fetch css classes.
-  (xwidget-plus-js-inject (xwidget-webkit-current-session) 'test)
-  (xwidget-plus-test-element-classes (xwidget-webkit-current-session) "a" #'xwidget-plus-update-fn-callback)
-  (xwidget-plus-event-dispatch))
+  (xwwp-js-inject (xwidget-webkit-current-session) 'test)
+  (xwwp-test-element-classes (xwidget-webkit-current-session) "a" #'xwwp-update-fn-callback)
+  (xwwp-event-dispatch))
 
-(cl-defmethod backend-test-link-classes ((backend xwidget-plus-completion-backend-test) link-id)
+(cl-defmethod backend-test-link-classes ((backend xwwp-follow-link-completion-backend-test) link-id)
   "Return test BACKEND css class names for LINK-ID."
   (cdr (assoc link-id (oref backend classes))))
 
 (defmacro with-backend (backend &rest body)
   "Run BODY with the specified BACKEND."
   (declare (indent 1))
-  `(let* ((backend (,(intern (concat "xwidget-plus-completion-backend-" (symbol-name backend)))))
-          (xwidget-plus-follow-link-completion-backend-instance backend))
+  `(let* ((backend (,(intern (concat "xwwp-follow-link-completion-backend-" (symbol-name backend)))))
+          (xwwp-follow-link-completion-backend-instance backend))
      ,@body))
 
 
 (defmacro with-test-backend-browse (candidates selected url &rest body)
   "Run BODY with the specified BACKEND mocking CANDIDATES and SELECTED while browsing URL."
   (declare (indent 3))
-  `(let* ((xwidget-plus-completion-system (lambda () (xwidget-plus-completion-backend-test :candidates-mock ,candidates
-                                                                                           :selected-mock ,selected))))
+  `(let* ((xwwp-follow-link-completion-system
+           (lambda () (xwwp-follow-link-completion-backend-test :candidates-mock ,candidates
+                                                                :selected-mock ,selected))))
      (with-browse ,url
        ,@body)))
 
-(ert-deftest test-xwidget-plus-follow-link-prepare-links ()
+(ert-deftest test-xwwp-follow-link-prepare-links ()
   (let ((links '(("3" . "Functions")
                  ("1" . "Function Cells")
                  ("12" . "Structures")
@@ -114,41 +119,41 @@ return '' + window.location;
 	             ("Functions" . 3)
 	             ("Declare Form" . 9)
 	             ("Structures" . 12))
-            (xwidget-plus-follow-link-prepare-links links)))))
+            (xwwp-follow-link-prepare-links links)))))
 
-(ert-deftest test-xwidget-plus-follow-link-highlight ()
+(ert-deftest test-xwwp-follow-link-highlight ()
   (with-test-backend-browse '(0 0 1) 0 "links.html"
-    (xwidget-plus-follow-link)
-    (xwidget-plus-event-loop)
-    (let ((backend xwidget-plus-follow-link-completion-backend-instance))
-      (xwidget-plus-js-inject xwidget 'test)
-      (xwidget-plus-test-current-location xwidget #'xwidget-plus-location-callback)
-      (xwidget-plus-event-dispatch)
+    (xwwp-follow-link)
+    (xwwp-event-loop)
+    (let ((backend xwwp-follow-link-completion-backend-instance))
+      (xwwp-js-inject xwidget 'test)
+      (xwwp-test-current-location xwidget #'xwwp-location-callback)
+      (xwwp-event-dispatch)
       (should (string= "test-1.html" (file-name-nondirectory (oref backend location))))
-      (should (equal (backend-test-link-classes backend "test-1") '["xwidget-plus-follow-link-selected"]))
-      (should (equal (backend-test-link-classes backend "test-2") '["xwidget-plus-follow-link-candidate"]))))
+      (should (equal (backend-test-link-classes backend "test-1") '["xwwp-follow-link-selected"]))
+      (should (equal (backend-test-link-classes backend "test-2") '["xwwp-follow-link-candidate"]))))
   (with-test-backend-browse '(1 0 1) 1 "links.html"
-    (xwidget-plus-follow-link)
-    (xwidget-plus-event-loop)
-    (let ((backend xwidget-plus-follow-link-completion-backend-instance))
-      (xwidget-plus-js-inject xwidget 'test)
-      (xwidget-plus-test-current-location xwidget #'xwidget-plus-location-callback)
-      (xwidget-plus-event-dispatch)
+    (xwwp-follow-link)
+    (xwwp-event-loop)
+    (let ((backend xwwp-follow-link-completion-backend-instance))
+      (xwwp-js-inject xwidget 'test)
+      (xwwp-test-current-location xwidget #'xwwp-location-callback)
+      (xwwp-event-dispatch)
       (should (string= "test-2.html" (file-name-nondirectory (oref backend location))))
-      (should (equal (backend-test-link-classes backend "test-1") '["xwidget-plus-follow-link-candidate"]))
-      (should (equal (backend-test-link-classes backend "test-2") '["xwidget-plus-follow-link-selected"])))))
+      (should (equal (backend-test-link-classes backend "test-1") '["xwwp-follow-link-candidate"]))
+      (should (equal (backend-test-link-classes backend "test-2") '["xwwp-follow-link-selected"])))))
 
-(ert-deftest test-xwidget-plus-follow-link-highlight-no-candidates ()
+(ert-deftest test-xwwp-follow-link-highlight-no-candidates ()
   (with-test-backend-browse '(1) 1 "links.html"
-    (xwidget-plus-follow-link)
-    (xwidget-plus-event-loop)
-    (let ((backend xwidget-plus-follow-link-completion-backend-instance))
-      (xwidget-plus-js-inject xwidget 'test)
-      (xwidget-plus-test-current-location xwidget #'xwidget-plus-location-callback)
-      (xwidget-plus-event-dispatch)
+    (xwwp-follow-link)
+    (xwwp-event-loop)
+    (let ((backend xwwp-follow-link-completion-backend-instance))
+      (xwwp-js-inject xwidget 'test)
+      (xwwp-test-current-location xwidget #'xwwp-location-callback)
+      (xwwp-event-dispatch)
       (should (string= "test-2.html" (file-name-nondirectory (oref backend location))))
       (should (equal (backend-test-link-classes backend "test-1") '[]))
-      (should (equal (backend-test-link-classes backend "test-2") '["xwidget-plus-follow-link-selected"])))))
+      (should (equal (backend-test-link-classes backend "test-2") '["xwwp-follow-link-selected"])))))
 
 (defmacro with-read-fixtures (backend &rest body)
   (declare (indent 1))
@@ -160,76 +165,77 @@ return '' + window.location;
        (with-browse "links.html"
          ,@body))))
 
-(ert-deftest test-xwidget-plus-follow-link-read-default ()
+(ert-deftest test-xwwp-follow-link-read-default ()
   (with-read-fixtures default
     (with-simulated-input "test SPC 2 RET"
-      (xwidget-plus-follow-link-read backend "Test: " links action update)
+      (xwwp-follow-link-read backend "Test: " links action update)
       (should (= 1 link)))))
 
-(ert-deftest test-xwidget-plus-follow-link-read-ido ()
+(ert-deftest test-xwwp-follow-link-read-ido ()
   (require 'ido)
   (with-read-fixtures ido
     (with-simulated-input "2 RET"
-      (xwidget-plus-follow-link-read backend "Test: " links action update)
+      (xwwp-follow-link-read backend "Test: " links action update)
       (should (= 1 link)))))
 
-(ert-deftest test-xwidget-plus-follow-link-read-ivy ()
+(ert-deftest test-xwwp-follow-link-read-ivy ()
   (require 'ivy)
   (with-read-fixtures ivy
     (with-simulated-input "2 RET"
-      (xwidget-plus-follow-link-read backend "Test: " links action update)
+      (xwwp-follow-link-read backend "Test: " links action update)
       (should (= 1 link)))))
 
-(ert-deftest test-xwidget-plus-follow-link-read-helm ()
+(ert-deftest test-xwwp-follow-link-read-helm ()
   (require 'helm)
   (with-read-fixtures helm
     (with-simulated-input '("2" (wsi-simulate-idle-time 0.1) "RET")
-      (xwidget-plus-follow-link-read backend "Test: " links action update)
+      (xwwp-follow-link-read backend "Test: " links action update)
       (should (= 1 link)))))
 
 (defmacro with-feature (feature &rest body)
   (declare (indent 1))
-  `(progn (when (featurep 'ido) (unload-feature 'ido t))
-          (when (featurep 'ivy) (unload-feature 'ivy t))
-          (when (featurep 'helm) (unload-feature 'helm t))
-          (when ,feature (require ,feature))
-          ,@body))
+  (let ((fsym (intern (concat "xwwp-follow-link-" (symbol-name feature)))))
+    `(cl-letf (((symbol-function 'require) (lambda (f &optional filename no-errors) (eq (quote ,fsym) f))))
+       ,@body)))
 
-(ert-deftest test-xwidget-plus-follow-link-make-backend-use-feature ()
+(ert-deftest test-xwwp-follow-link-make-backend-use-feature ()
   (with-feature nil
-    (should (eq #'xwidget-plus-completion-backend-default (xwidget-plus-follow-link-make-backend))))
-  (with-feature 'ido
-    (should (eq #'xwidget-plus-completion-backend-ido (xwidget-plus-follow-link-make-backend))))
-  (with-feature 'ivy
-    (should (eq #'xwidget-plus-completion-backend-ivy (xwidget-plus-follow-link-make-backend))))
-  (with-feature 'helm
-    (should (eq #'xwidget-plus-completion-backend-helm (xwidget-plus-follow-link-make-backend)))))
-
-(ert-deftest test-xwidget-plus-follow-link-make-backend-use-custom ()
-  (let ((xwidget-plus-completion-system 'default))
+    (should (eq #'xwwp-follow-link-completion-backend-default (xwwp-follow-link-make-backend))))
+  (with-feature ido
+    (should (eq #'xwwp-follow-link-completion-backend-ido (xwwp-follow-link-make-backend))))
+  (with-feature ivy
+    (should (eq #'xwwp-follow-link-completion-backend-ivy (xwwp-follow-link-make-backend))))
+  (with-feature helm
+    (should (eq #'xwwp-follow-link-completion-backend-helm (xwwp-follow-link-make-backend)))))
+
+(ert-deftest test-xwwp-follow-link-make-backend-use-custom ()
+  (let ((xwwp-follow-link-completion-system 'default))
     (with-feature nil
-      (should (eq #'xwidget-plus-completion-backend-default (xwidget-plus-follow-link-make-backend)))))
-  (let ((xwidget-plus-completion-system 'ido))
-    (should (eq #'xwidget-plus-completion-backend-ido (xwidget-plus-follow-link-make-backend))))
-  (let ((xwidget-plus-completion-system 'ivy))
-    (should (eq #'xwidget-plus-completion-backend-ivy (xwidget-plus-follow-link-make-backend))))
-  (let ((xwidget-plus-completion-system 'helm))
-    (should (eq #'xwidget-plus-completion-backend-helm (xwidget-plus-follow-link-make-backend))))
-  (let ((xwidget-plus-completion-system #'identity))
-    (should (eq #'identity (xwidget-plus-follow-link-make-backend)))))
+      (should (eq #'xwwp-follow-link-completion-backend-default (xwwp-follow-link-make-backend)))))
+  (let ((xwwp-follow-link-completion-system 'ido))
+    (with-feature ido
+      (should (eq #'xwwp-follow-link-completion-backend-ido (xwwp-follow-link-make-backend)))))
+  (let ((xwwp-follow-link-completion-system 'ivy))
+    (with-feature ivy
+      (should (eq #'xwwp-follow-link-completion-backend-ivy (xwwp-follow-link-make-backend)))))
+  (let ((xwwp-follow-link-completion-system 'helm))
+    (with-feature helm
+      (should (eq #'xwwp-follow-link-completion-backend-helm (xwwp-follow-link-make-backend)))))
+  (let ((xwwp-follow-link-completion-system #'identity))
+    (should (eq #'identity (xwwp-follow-link-make-backend)))))
 
 ;; Local Variables:
 ;; eval: (mmm-mode)
 ;; eval: (mmm-add-group 'elisp-js '((elisp-rawjs :submode js-mode
 ;;                                               :face mmm-code-submode-face
 ;;                                               :delimiter-mode nil
-;;                                               :front "xwidget-plus--js \"" :back "\" js--")
+;;                                               :front "xwwp--js \"" :back "\" js--")
 ;;                                  (elisp-defjs :submode js-mode
 ;;                                               :face mmm-code-submode-face
 ;;                                               :delimiter-mode nil
-;;                                               :front "xwidget-plus-js-def .*\n.*\"\"\n" :back "\")\n")))
+;;                                               :front "xwwp-js-def .*\n.*\"\"\n" :back "\")\n")))
 ;; mmm-classes: elisp-js
 ;; End:
 
-(provide 'xwidget-plus-follow-link-test)
-;;; xwidget-plus-follow-link-test.el ends here
+(provide 'xwwp-follow-link-test)
+;;; xwwp-follow-link-test.el ends here

+ 0 - 324
xwidget-plus-follow-link.el

@@ -1,324 +0,0 @@
-;;; xwidget-plus-follow-link.el --- Link navigation in browsers -*- lexical-binding: t; -*-
-
-;; Copyright (C) 2020 Damien Merenne <dam@cosinux.org>
-
-;; This file is NOT part of GNU Emacs.
-
-;;; Commentary:
-
-;; Add support for navigating web pages using the minibuffer completion.
-
-;;; License:
-
-;; This program is free software: you can redistribute it and/or modify
-;; it under the terms of the GNU General Public License as published by
-;; the Free Software Foundation, either version 3 of the License, or
-;; (at your option) any later version.
-
-;; This program is distributed in the hope that it will be useful,
-;; but WITHOUT ANY WARRANTY; without even the implied warranty of
-;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-;; GNU General Public License for more details.
-
-;; You should have received a copy of the GNU General Public License
-;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
-
-;;; Commentary:
-
-;;
-
-;;; Code:
-
-(require 'xwidget)
-(require 'xwidget-plus-common)
-(require 'eieio)
-(require 'cl-lib)
-
-(defcustom xwidget-plus-follow-link-candidate-style '(("border" . "1px dashed blue")
-                                                      ("background" . "#0000ff20"))
-  "Style to apply to candidate links."
-  :type '(list (cons string string))
-  :group 'xwidget-plus)
-
-(defcustom xwidget-plus-follow-link-selected-style '(("border" . "1px dashed red")
-                                                     ("background" . "#ff000020"))
-  "Style to apply to currently selected link."
-  :type '(list (cons string string))
-  :group 'xwidget-plus)
-
-(defun xwidget-plus-follow-link-style-definition ()
-  "Return the css definitions for the follow link feature."
-  (concat (xwidget-plus-make-class "xwidget-plus-follow-link-candidate" xwidget-plus-follow-link-candidate-style)
-          (xwidget-plus-make-class "xwidget-plus-follow-link-selected" xwidget-plus-follow-link-selected-style)))
-
-(xwidget-plus-js-def follow-link cleanup ()
-  "Remove all custom class from links.""
-document.querySelectorAll('a').forEach(a => {
-    a.classList.remove('xwidget-plus-follow-link-candidate', 'xwidget-plus-follow-link-selected');
-});
-")
-
-(xwidget-plus-js-def follow-link highlight (ids selected)
-  "Highlight IDS as candidate and SELECTED as selected.""
-document.querySelectorAll('a').forEach((a, id) => {
-    a.classList.remove('xwidget-plus-follow-link-candidate', 'xwidget-plus-follow-link-selected');
-    if (selected == id) {
-        a.classList.add('xwidget-plus-follow-link-selected');
-        a.scrollIntoView({behavior: 'smooth', block: 'center'});
-    } else if (ids && ids.includes(id)) {
-        a.classList.add('xwidget-plus-follow-link-candidate');
-    }
-});
-")
-
-(xwidget-plus-js-def follow-link action (link-id)
-  "Click on the link identified by LINK-ID""
-__xwidget_plus_follow_link_cleanup();
-document.querySelectorAll('a')[link_id].click();
-")
-
-(xwidget-plus-js-def follow-link fetch-links ()
-  "Fetch all visible, non empty links from the current page.""
-var r = {};
-document.querySelectorAll('a').forEach((a, i) => {
-    if (a.offsetWidth || a.offsetHeight || a.getClientRects().length) {
-        if (a.innerText.match(/\\\\S/))
-            r[i] = a.innerText;
-    }
-});
-return r;
-")
-
-
-;; Completion backend class
-(defclass xwidget-plus-completion-backend () ((collection) (text)))
-
-(cl-defmethod xwidget-plus-follow-link-candidates ((_backend xwidget-plus-completion-backend))
-    "Return the BACKEND selected link and the candidates.
-
-The return value is a list whose first element is the selected id
-link and the rest are the candidates ids.
-
-Return nil if the backend does not support narrowing selection list.")
-
-(cl-defmethod xwidget-plus-follow-link-read ((_backend xwidget-plus-completion-backend)
-                                             _prompt _collection _action _update-fn)
-  "Use BACKEND to PROMPT the user for a link in COLLECTION.
-
-ACTION should be called with the resulting link.
-
-UPDATE-FN is a function that can be called when the candidates
-list is narrowed.It will highlight the link list in the
-browser.")
-
-
-;; Default backend using completing-read
-(defclass xwidget-plus-completion-backend-default (xwidget-plus-completion-backend) ())
-
-(cl-defmethod xwidget-plus-follow-link-candidates ((backend xwidget-plus-completion-backend-default))
-    "Return the BACKEND selected link and the candidates.
-
-The return value is a list whose first element is the selected id
-link and the rest are the candidates ids.
-
-Return nil if the backend does not support narrowing selection list."
-    (let* ((collection (oref backend collection))
-           (text (oref backend text))
-           (matches (seq-filter (lambda (i) (string-match-p (concat "^" (regexp-quote text)) (car i))) collection))
-           (matches (seq-map #'cdr matches)))
-      (if (= 1 (length matches))
-          matches
-        (cons nil matches))))
-
-(cl-defmethod xwidget-plus-follow-link-read ((backend xwidget-plus-completion-backend-default) prompt collection action update-fn)
-  "Use BACKEND to PROMPT the user for a link in COLLECTION.
-
-ACTION should be called with the resulting link.
-
-UPDATE-FN is a function that can be called when the candidates
-list is narrowed.It will highlight the link list in the
-browser."
-  (funcall action (cdr (assoc (completing-read prompt (lambda (str pred _)
-                                                        (oset backend text str)
-                                                        (funcall update-fn)
-                                                        (try-completion str collection pred))
-                                               nil t)
-                              collection))))
-
-
-;; Ido backend using ido-completing-read
-(with-eval-after-load 'ido
-   ;; tell the compiler these do exists
-  (defvar ido-matches)
-  (declare-function ido-set-matches "ido")
-
-  (defclass xwidget-plus-completion-backend-ido (xwidget-plus-completion-backend) ())
-
-  (cl-defmethod xwidget-plus-follow-link-candidates ((backend xwidget-plus-completion-backend-ido))
-    (let ((collection (oref backend collection)))
-      (when collection
-        (seq-map (lambda (i) (cdr (assoc i collection))) ido-matches))))
-
-  (cl-defmethod xwidget-plus-follow-link-read ((backend xwidget-plus-completion-backend-ido) prompt collection action update-fn)
-    (let ((choices (seq-map #'car collection)))
-      (advice-add #'ido-set-matches :after update-fn)
-      (let ((link (unwind-protect
-                      (cdr (assoc (ido-completing-read prompt choices nil t) collection))
-                    (oset backend collection nil)
-                    (advice-remove #'ido-set-matches update-fn))))
-        (funcall action link)))))
-
-
-;; Ivy backend using completing read
-(with-eval-after-load 'ivy
-   ;; tell the compiler these do exists
-  (defvar ivy-last)
-  (defvar ivy-text)
-  (defvar ivy--all-candidates)
-  (declare-function ivy-read "ivy")
-  (declare-function ivy-state-buffer "ivy")
-  (declare-function ivy-state-collection "ivy")
-  (declare-function ivy-state-current "ivy")
-  (declare-function ivy--filter "ivy")
-
-  (defclass xwidget-plus-completion-backend-ivy (xwidget-plus-completion-backend) ())
-
-  (cl-defmethod xwidget-plus-follow-link-candidates ((_ xwidget-plus-completion-backend-ivy))
-    (with-current-buffer (ivy-state-buffer ivy-last)
-      (let* ((collection (ivy-state-collection ivy-last))
-             (current (ivy-state-current ivy-last))
-             (candidates (ivy--filter ivy-text ivy--all-candidates))
-             (result (cons current candidates)))
-        (seq-map (lambda (c) (cdr (nth (get-text-property 0 'idx c) collection))) result))))
-
-  (cl-defmethod xwidget-plus-follow-link-read ((_ xwidget-plus-completion-backend-ivy) prompt collection action update-fn)
-    (ivy-read prompt collection :require-match t :action (lambda (v) (funcall action (cdr v))) :update-fn update-fn)))
-
-
-;; Helm backend
-(with-eval-after-load 'helm
-   ;; tell the compiler these do exists
-  (declare-function helm "helm")
-  (declare-function helm-get-selection "helm")
-  (declare-function helm-make-source "helm-source")
-
-  (defclass xwidget-plus-completion-backend-helm (xwidget-plus-completion-backend) ((candidates)))
-
-  (cl-defmethod xwidget-plus-follow-link-candidates ((backend xwidget-plus-completion-backend-helm))
-    (let* ((candidates (oref backend candidates))
-           (selection (helm-get-selection))
-           (selected (when selection (cdr (elt (oref backend collection) selection))))
-           (result (seq-map #'cdr candidates)))
-      (cons selected result)))
-
-  (cl-defmethod xwidget-plus-follow-link-read ((backend xwidget-plus-completion-backend-helm) prompt collection action update-fn)
-    (add-hook 'helm-after-initialize-hook (lambda ()
-                                            (with-current-buffer "*helm-xwidget-plus*"
-                                              (add-hook 'helm-move-selection-after-hook update-fn nil t)))
-              nil t)
-    (helm :sources
-          (helm-make-source "Xwidget Plus" 'helm-source-sync
-                            :candidates collection
-                            :action action
-                            :filtered-candidate-transformer (lambda (candidates _)
-                                                              (oset backend candidates candidates)
-                                                              (funcall update-fn)
-                                                              candidates))
-          :prompt prompt
-          :buffer "*helm-xwidget-plus*")))
-
-;; Tell the compiler that the backend function exists
-(declare-function xwidget-plus-completion-backend-ido "xwidget-plus-follow-link")
-(declare-function xwidget-plus-completion-backend-ido--eieio-childp "xwidget-plus-follow-link")
-(declare-function xwidget-plus-completion-backend-ivy "xwidget-plus-follow-link")
-(declare-function xwidget-plus-completion-backend-ivy--eieio-childp "xwidget-plus-follow-link")
-(declare-function xwidget-plus-completion-backend-helm "xwidget-plus-follow-link")
-(declare-function xwidget-plus-completion-backend-helm--eieio-childp "xwidget-plus-follow-link")
-
-(defun xwidget-plus-follow-link-make-backend ()
-  "Instanciate a completion backend."
-  (cond ((eq xwidget-plus-completion-system 'default)
-         (cond ((featurep 'ivy)
-                #'xwidget-plus-completion-backend-ivy)
-               ((featurep 'helm)
-                #'xwidget-plus-completion-backend-helm)
-               ((featurep 'ido)
-                #'xwidget-plus-completion-backend-ido)
-               (t #'xwidget-plus-completion-backend-default)))
-        ((eq xwidget-plus-completion-system 'ivy)
-         #'xwidget-plus-completion-backend-ivy)
-        ((eq xwidget-plus-completion-system 'helm)
-         #'xwidget-plus-completion-backend-helm)
-        ((eq xwidget-plus-completion-system 'ido)
-         #'xwidget-plus-completion-backend-ido)
-        ((eq xwidget-plus-completion-system 'default)
-         #'xwidget-plus-completion-backend-default)
-        (t xwidget-plus-completion-system)))
-
-
-(defvar xwidget-plus-follow-link-completion-backend-instance '())
-
-(defun xwidget-plus-follow-link-update (xwidget)
-  "Highligh LINKS in XWIDGET buffer when updating candidates."
-  (let ((links (xwidget-plus-follow-link-candidates xwidget-plus-follow-link-completion-backend-instance)))
-    (when links
-      (let* ((selected (car links))
-             (candidates (cdr links)))
-        (xwidget-plus-follow-link-highlight xwidget candidates selected)))))
-
-(defun xwidget-plus-follow-link-trigger-action (xwidget selected)
-  "Activate link matching SELECTED in XWIDGET LINKS."
-  (xwidget-plus-follow-link-action xwidget selected))
-
-(defun xwidget-plus-follow-link-format-link (str)
-  "Format link title STR."
-  (setq str (replace-regexp-in-string "^[[:space:][:cntrl:]]+" "" str))
-  (setq str (replace-regexp-in-string "[[:space:][:cntrl:]]+$" "" str))
-  (setq str (replace-regexp-in-string "[[:cntrl:]]+" "/" str))
-  (replace-regexp-in-string "[[:space:]]+" " " str))
-
-(defun xwidget-plus-follow-link-prepare-links (links)
-  "Prepare the alist of LINKS."
-  (seq-sort-by (lambda (v) (cdr v)) #'<
-               (seq-map (lambda (v) (cons (xwidget-plus-follow-link-format-link (cdr v)) (string-to-number (car v))))
-                        links)))
-
-(defun xwidget-plus-follow-link-callback (links)
-  "Ask for a link belonging to the alist LINKS."
-  (let* ((xwidget (xwidget-webkit-current-session))
-         (links (xwidget-plus-follow-link-prepare-links links)))
-    (oset xwidget-plus-follow-link-completion-backend-instance collection links)
-    (unwind-protect
-        (condition-case nil
-            (xwidget-plus-follow-link-read xwidget-plus-follow-link-completion-backend-instance
-                                           "Link: " links
-                                           (apply-partially #'xwidget-plus-follow-link-trigger-action xwidget)
-                                           (apply-partially #'xwidget-plus-follow-link-update xwidget))
-          (quit (xwidget-plus-follow-link-cleanup xwidget))))
-    (oset xwidget-plus-follow-link-completion-backend-instance collection nil)))
-
-;;;###autoload
-(defun xwidget-plus-follow-link (&optional xwidget)
-  "Ask for a link in the XWIDGET session or the current one and follow it."
-  (interactive)
-  (setq xwidget-plus-follow-link-completion-backend-instance (funcall (xwidget-plus-follow-link-make-backend)))
-  (let ((xwidget (or xwidget (xwidget-webkit-current-session))))
-    (xwidget-plus-inject-style xwidget "__xwidget_plus_follow_link_style" (xwidget-plus-follow-link-style-definition))
-    (xwidget-plus-js-inject xwidget 'follow-link)
-    (xwidget-plus-follow-link-fetch-links xwidget #'xwidget-plus-follow-link-callback)))
-
-;; Local Variables:
-;; eval: (mmm-mode)
-;; eval: (mmm-add-group 'elisp-js '((elisp-rawjs :submode js-mode
-;;                                               :face mmm-code-submode-face
-;;                                               :delimiter-mode nil
-;;                                               :front "xwidget-plus--js \"" :back "\" js--")
-;;                                  (elisp-defjs :submode js-mode
-;;                                               :face mmm-code-submode-face
-;;                                               :delimiter-mode nil
-;;                                               :front "xwidget-plus-js-def .*\n.*\"\"\n" :back "\")\n")))
-;; mmm-classes: elisp-js
-;; End:
-
-(provide 'xwidget-plus-follow-link)
-;;; xwidget-plus-follow-link.el ends here

+ 0 - 55
xwidget-plus.el

@@ -1,55 +0,0 @@
-;;; xwidget-plus.el --- Improve xwidget usability -*- lexical-binding: t; -*-
-
-;; Author: Damien Merenne
-;; URL: https://github.com/canatella/xwidget-plus
-;; Created: 2020-03-11
-;; Keywords: convenience
-;; Version: 0.1
-;; Package-Requires: ((emacs "26.1"))
-
-;; This file is NOT part of GNU Emacs.
-
-;;; Commentary:
-
-;; This package augment the xwidget-webkit browser to make it more usable.
-
-;;; License:
-
-;; This program is free software: you can redistribute it and/or modify
-;; it under the terms of the GNU General Public License as published by
-;; the Free Software Foundation, either version 3 of the License, or
-;; (at your option) any later version.
-
-;; This program is distributed in the hope that it will be useful,
-;; but WITHOUT ANY WARRANTY; without even the implied warranty of
-;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-;; GNU General Public License for more details.
-
-;; You should have received a copy of the GNU General Public License
-;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
-
-;;; Code:
-
-(require 'xwidget-plus-common)
-(require 'xwidget-plus-follow-link)
-
-;;;###autoload
-(defun xwidget-plus-browse-url (url &optional new-session)
-  "Ask xwidget-webkit to browse URL.
-NEW-SESSION specifies whether to create a new xwidget-webkit session.
-Interactively, URL defaults to the string looking like a url around point."
-  (interactive (progn
-                 (require 'browse-url)
-                 (browse-url-interactive-arg "xwidget-webkit URL: "
-                                             ;;(xwidget-webkit-current-url)
-                                             )))
-  (or (featurep 'xwidget-internal)
-      (user-error "Your Emacs was not compiled with xwidgets support"))
-  (when (stringp url)
-    (if new-session
-        (xwidget-webkit-new-session url)
-      (progn (xwidget-webkit-goto-url url)
-             (switch-to-buffer-other-window (xwidget-buffer (xwidget-webkit-current-session)))))))
-
-(provide 'xwidget-plus)
-;;; xwidget-plus.el ends here

+ 70 - 0
xwwp-follow-link-helm.el

@@ -0,0 +1,70 @@
+;;; xwwp-follow-link-helm.el --- Link navigation in `xwidget-webkit' sessions using `helm' -*- lexical-binding: t; -*-
+
+;; Author: Damien Merenne
+;; URL: https://github.com/canatella/xwwp
+;; Created: 2020-03-11
+;; Keywords: convenience
+;; Version: 0.1
+;; Package-Requires: ((emacs "26.1") (xwwp-follow-link "0.1"))
+
+;; Copyright (C) 2020 Damien Merenne <dam@cosinux.org>
+
+;; This file is NOT part of GNU Emacs.
+
+;;; License:
+
+;; This program is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Add support for navigating web pages in `xwidget-webkit' sessions using the
+;; `helm' completion.
+
+;;; Code:
+
+(require 'xwwp-follow-link)
+(require 'helm)
+
+;; tell the compiler these do exists
+(declare-function helm "helm")
+(declare-function helm-get-selection "helm")
+(declare-function helm-make-source "helm-source")
+
+(defclass xwwp-follow-link-completion-backend-helm (xwwp-follow-link-completion-backend) ((candidates)))
+
+(cl-defmethod xwwp-follow-link-candidates ((backend xwwp-follow-link-completion-backend-helm))
+  (let* ((candidates (oref backend candidates))
+         (selection (helm-get-selection))
+         (selected (when selection (cdr (elt (oref backend collection) selection))))
+         (result (seq-map #'cdr candidates)))
+    (cons selected result)))
+
+(cl-defmethod xwwp-follow-link-read ((backend xwwp-follow-link-completion-backend-helm) prompt collection action update-fn)
+  (add-hook 'helm-after-initialize-hook (lambda ()
+                                          (with-current-buffer "*helm-xwwp*"
+                                            (add-hook 'helm-move-selection-after-hook update-fn nil t)))
+            nil t)
+  (helm :sources
+        (helm-make-source "Xwidget Plus" 'helm-source-sync
+                          :candidates collection
+                          :action action
+                          :filtered-candidate-transformer (lambda (candidates _)
+                                                            (oset backend candidates candidates)
+                                                            (funcall update-fn)
+                                                            candidates))
+        :prompt prompt
+        :buffer "*helm-xwwp*"))
+
+(provide 'xwwp-follow-link-helm)
+;;; xwwp-follow-link-helm.el ends here

+ 56 - 0
xwwp-follow-link-ido.el

@@ -0,0 +1,56 @@
+;;; xwwp-follow-link-ido.el --- Link navigation in `xwidget-webkit' sessions using `ido' -*- lexical-binding: t; -*-
+
+;; Author: Damien Merenne
+;; URL: https://github.com/canatella/xwwp
+;; Created: 2020-03-11
+;; Keywords: convenience
+;; Version: 0.1
+;; Package-Requires: ((emacs "26.1") (xwwp-follow-link "0.1"))
+
+;; Copyright (C) 2020 Damien Merenne <dam@cosinux.org>
+
+;; This file is NOT part of GNU Emacs.
+
+;;; License:
+
+;; This program is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Add support for navigating web pages in `xwidget-webkit' sessions using the
+;; `ido' completion.
+
+;;; Code:
+
+(require 'xwwp-follow-link)
+(require 'ido)
+
+(defclass xwwp-follow-link-completion-backend-ido (xwwp-follow-link-completion-backend) ())
+
+(cl-defmethod xwwp-follow-link-candidates ((backend xwwp-follow-link-completion-backend-ido))
+  (let ((collection (oref backend collection)))
+    (when collection
+      (seq-map (lambda (i) (cdr (assoc i collection))) ido-matches))))
+
+(cl-defmethod xwwp-follow-link-read ((backend xwwp-follow-link-completion-backend-ido) prompt collection action update-fn)
+  (let ((choices (seq-map #'car collection)))
+    (advice-add #'ido-set-matches :after update-fn)
+    (let ((link (unwind-protect
+                    (cdr (assoc (ido-completing-read prompt choices nil t) collection))
+                  (oset backend collection nil)
+                  (advice-remove #'ido-set-matches update-fn))))
+      (funcall action link))))
+
+(provide 'xwwp-follow-link-ido)
+;;; xwwp-follow-link-ido.el ends here

+ 53 - 0
xwwp-follow-link-ivy.el

@@ -0,0 +1,53 @@
+;;; xwwp-follow-link-ivy.el --- Link navigation in `xwidget-webkit' sessions using `ivy' -*- lexical-binding: t; -*-
+
+;; Author: Damien Merenne
+;; URL: https://github.com/canatella/xwwp
+;; Created: 2020-03-11
+;; Keywords: convenience
+;; Version: 0.1
+;; Package-Requires: ((emacs "26.1") (xwwp-follow-link "0.1"))
+
+;; Copyright (C) 2020 Damien Merenne <dam@cosinux.org>
+
+;; This file is NOT part of GNU Emacs.
+
+;;; License:
+
+;; This program is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Add support for navigating web pages in `xwidget-webkit' sessions using the
+;; `ivy' completion.
+
+;;; Code:
+
+(require 'xwwp-follow-link)
+(require 'ivy)
+
+(defclass xwwp-follow-link-completion-backend-ivy (xwwp-follow-link-completion-backend) ())
+
+(cl-defmethod xwwp-follow-link-candidates ((_ xwwp-follow-link-completion-backend-ivy))
+  (with-current-buffer (ivy-state-buffer ivy-last)
+    (let* ((collection (ivy-state-collection ivy-last))
+           (current (ivy-state-current ivy-last))
+           (candidates (ivy--filter ivy-text ivy--all-candidates))
+           (result (cons current candidates)))
+      (seq-map (lambda (c) (cdr (nth (get-text-property 0 'idx c) collection))) result))))
+
+(cl-defmethod xwwp-follow-link-read ((_ xwwp-follow-link-completion-backend-ivy) prompt collection action update-fn)
+  (ivy-read prompt collection :require-match t :action (lambda (v) (funcall action (cdr v))) :update-fn update-fn))
+
+(provide 'xwwp-follow-link-ivy)
+;;; xwwp-follow-link-ivy.el ends here

+ 267 - 0
xwwp-follow-link.el

@@ -0,0 +1,267 @@
+;;; xwwp-follow-link.el --- Link navigation in `xwidget-webkit' sessions -*- lexical-binding: t; -*-
+
+;; Author: Damien Merenne
+;; URL: https://github.com/canatella/xwwp
+;; Created: 2020-03-11
+;; Keywords: convenience
+;; Version: 0.1
+;; Package-Requires: ((emacs "26.1") (xwwp "0.1"))
+
+;; Copyright (C) 2020 Damien Merenne <dam@cosinux.org>
+
+;; This file is NOT part of GNU Emacs.
+
+;;; License:
+
+;; This program is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Add support for navigating web pages in `xwidget-webkit' sessions using the
+;; minibuffer completion.
+
+;;; Code:
+
+(require 'xwidget)
+(require 'xwwp)
+(require 'eieio)
+(require 'cl-lib)
+(require 'ido)
+
+(defgroup xwwp-follow-link nil
+  "`xwidget-webkit' follow link customizations."
+  :group 'xwwp)
+
+(defcustom xwwp-follow-link-completion-system 'default
+  "The completion system to be used by xwidget plus.
+
+Custom function should be a function that takes no arguments and
+returns an instance of an eieio class extending
+`xwwp-follow-link-completion-backend'."
+  :group 'xwwp-follow-link
+  :type '(radio
+          (const :tag "Ido" ido)
+          (const :tag "Helm" helm)
+          (const :tag "Ivy" ivy)
+          (const :tag "Default" default)
+          (function :tag "Custom function")))
+
+(defcustom xwwp-follow-link-candidate-style '(("border" . "1px dashed blue")
+                                              ("background" . "#0000ff20"))
+  "Style to apply to candidate links."
+  :type '(list (cons string string))
+  :group 'xwwp-follow-link)
+
+(defcustom xwwp-follow-link-selected-style '(("border" . "1px dashed red")
+                                             ("background" . "#ff000020"))
+  "Style to apply to currently selected link."
+  :type '(list (cons string string))
+  :group 'xwwp-follow-link)
+
+(defun xwwp-follow-link-style-definition ()
+  "Return the css definitions for the follow link feature."
+  (concat (xwwp-css-make-class "xwwp-follow-link-candidate" xwwp-follow-link-candidate-style)
+          (xwwp-css-make-class "xwwp-follow-link-selected" xwwp-follow-link-selected-style)))
+
+(xwwp-js-def follow-link cleanup ()
+  "Remove all custom class from links.""
+document.querySelectorAll('a').forEach(a => {
+    a.classList.remove('xwwp-follow-link-candidate', 'xwwp-follow-link-selected');
+});
+")
+
+(xwwp-js-def follow-link highlight (ids selected)
+  "Highlight IDS as candidate and SELECTED as selected.""
+document.querySelectorAll('a').forEach((a, id) => {
+    a.classList.remove('xwwp-follow-link-candidate', 'xwwp-follow-link-selected');
+    if (selected == id) {
+        a.classList.add('xwwp-follow-link-selected');
+        a.scrollIntoView({behavior: 'smooth', block: 'center'});
+    } else if (ids && ids.includes(id)) {
+        a.classList.add('xwwp-follow-link-candidate');
+    }
+});
+")
+
+(xwwp-js-def follow-link action (link-id)
+  "Click on the link identified by LINK-ID""
+__xwidget_plus_follow_link_cleanup();
+document.querySelectorAll('a')[link_id].click();
+")
+
+(xwwp-js-def follow-link fetch-links ()
+  "Fetch all visible, non empty links from the current page.""
+var r = {};
+document.querySelectorAll('a').forEach((a, i) => {
+    if (a.offsetWidth || a.offsetHeight || a.getClientRects().length) {
+        if (a.innerText.match(/\\\\S/))
+            r[i] = a.innerText;
+    }
+});
+return r;
+")
+
+
+;; Completion backend class
+(defclass xwwp-follow-link-completion-backend () ((collection) (text)))
+
+(cl-defmethod xwwp-follow-link-candidates ((_backend xwwp-follow-link-completion-backend))
+  "Return the BACKEND selected link and the candidates.
+
+The return value is a list whose first element is the selected id
+link and the rest are the candidates ids.
+
+Return nil if the backend does not support narrowing selection list.")
+
+(cl-defmethod xwwp-follow-link-read ((_backend xwwp-follow-link-completion-backend)
+                                     _prompt _collection _action _update-fn)
+  "Use BACKEND to PROMPT the user for a link in COLLECTION.
+
+ACTION should be called with the resulting link.
+
+UPDATE-FN is a function that can be called when the candidates
+list is narrowed.It will highlight the link list in the
+browser.")
+
+
+;; Default backend using completing-read
+(defclass xwwp-follow-link-completion-backend-default (xwwp-follow-link-completion-backend) ())
+
+(cl-defmethod xwwp-follow-link-candidates ((backend xwwp-follow-link-completion-backend-default))
+  "Return the BACKEND selected link and the candidates.
+
+The return value is a list whose first element is the selected id
+link and the rest are the candidates ids.
+
+Return nil if the backend does not support narrowing selection list."
+  (let* ((collection (oref backend collection))
+         (text (oref backend text))
+         (matches (seq-filter (lambda (i) (string-match-p (concat "^" (regexp-quote text)) (car i))) collection))
+         (matches (seq-map #'cdr matches)))
+    (if (= 1 (length matches))
+        matches
+      (cons nil matches))))
+
+(cl-defmethod xwwp-follow-link-read ((backend xwwp-follow-link-completion-backend-default) prompt collection action update-fn)
+  "Use BACKEND to PROMPT the user for a link in COLLECTION.
+
+ACTION should be called with the resulting link.
+
+UPDATE-FN is a function that can be called when the candidates
+list is narrowed.It will highlight the link list in the
+browser."
+  (funcall action (cdr (assoc (completing-read prompt (lambda (str pred _)
+                                                        (oset backend text str)
+                                                        (funcall update-fn)
+                                                        (try-completion str collection pred))
+                                               nil t)
+                              collection))))
+
+(declare-function xwwp-follow-link-completion-backend-ido "xwwp-follow-link-ido")
+(declare-function xwwp-follow-link-completion-backend-ivy "xwwp-follow-link-ivy")
+(declare-function xwwp-follow-link-completion-backend-helm "xwwp-follow-link-helm")
+
+(defun xwwp-follow-link-make-backend ()
+  "Instanciate a completion backend."
+  (cond ((eq xwwp-follow-link-completion-system 'default)
+         (cond ((require 'xwwp-follow-link-ivy nil t)
+                #'xwwp-follow-link-completion-backend-ivy)
+               ((require 'xwwp-follow-link-helm nil t)
+                #'xwwp-follow-link-completion-backend-helm)
+               ((require 'xwwp-follow-link-ido nil t)
+                #'xwwp-follow-link-completion-backend-ido)
+               (t #'xwwp-follow-link-completion-backend-default)))
+        ((eq xwwp-follow-link-completion-system 'ivy)
+         (unless (require 'xwwp-follow-link-ivy nil t)
+           (user-error "Install the `xwwp-follow-link-ivy' package to use `xwwp-follow-link' with `ivy'"))
+         #'xwwp-follow-link-completion-backend-ivy)
+        ((eq xwwp-follow-link-completion-system 'helm)
+         (unless (require 'xwwp-follow-link-helm nil t)
+           (user-error "Install the `xwwp-follow-link-helm' package to use `xwwp-follow-link' with `helm'"))
+         #'xwwp-follow-link-completion-backend-helm)
+        ((eq xwwp-follow-link-completion-system 'ido)
+         (unless (require 'xwwp-follow-link-ido nil t)
+           (user-error "Install the `xwwp-follow-link-ido' package to use `xwwp-follow-link' with `ido'"))
+         #'xwwp-follow-link-completion-backend-ido)
+        ((eq xwwp-follow-link-completion-system 'default)
+         #'xwwp-follow-link-completion-backend-default)
+        (t xwwp-follow-link-completion-system)))
+
+
+(defvar xwwp-follow-link-completion-backend-instance '())
+
+(defun xwwp-follow-link-update (xwidget)
+  "Highligh LINKS in XWIDGET buffer when updating candidates."
+  (let ((links (xwwp-follow-link-candidates xwwp-follow-link-completion-backend-instance)))
+    (when links
+      (let* ((selected (car links))
+             (candidates (cdr links)))
+        (xwwp-follow-link-highlight xwidget candidates selected)))))
+
+(defun xwwp-follow-link-trigger-action (xwidget selected)
+  "Activate link matching SELECTED in XWIDGET LINKS."
+  (xwwp-follow-link-action xwidget selected))
+
+(defun xwwp-follow-link-format-link (str)
+  "Format link title STR."
+  (setq str (replace-regexp-in-string "^[[:space:][:cntrl:]]+" "" str))
+  (setq str (replace-regexp-in-string "[[:space:][:cntrl:]]+$" "" str))
+  (setq str (replace-regexp-in-string "[[:cntrl:]]+" "/" str))
+  (replace-regexp-in-string "[[:space:]]+" " " str))
+
+(defun xwwp-follow-link-prepare-links (links)
+  "Prepare the alist of LINKS."
+  (seq-sort-by (lambda (v) (cdr v)) #'<
+               (seq-map (lambda (v) (cons (xwwp-follow-link-format-link (cdr v)) (string-to-number (car v))))
+                        links)))
+
+(defun xwwp-follow-link-callback (links)
+  "Ask for a link belonging to the alist LINKS."
+  (let* ((xwidget (xwidget-webkit-current-session))
+         (links (xwwp-follow-link-prepare-links links)))
+    (oset xwwp-follow-link-completion-backend-instance collection links)
+    (unwind-protect
+        (condition-case nil
+            (xwwp-follow-link-read xwwp-follow-link-completion-backend-instance
+                                   "Link: " links
+                                   (apply-partially #'xwwp-follow-link-trigger-action xwidget)
+                                   (apply-partially #'xwwp-follow-link-update xwidget))
+          (quit (xwwp-follow-link-cleanup xwidget))))
+    (oset xwwp-follow-link-completion-backend-instance collection nil)))
+
+;;;###autoload
+(defun xwwp-follow-link (&optional xwidget)
+  "Ask for a link in the XWIDGET session or the current one and follow it."
+  (interactive)
+  (setq xwwp-follow-link-completion-backend-instance (funcall (xwwp-follow-link-make-backend)))
+  (let ((xwidget (or xwidget (xwidget-webkit-current-session))))
+    (xwwp-html-inject-style xwidget "__xwidget_plus_follow_link_style" (xwwp-follow-link-style-definition))
+    (xwwp-js-inject xwidget 'follow-link)
+    (xwwp-follow-link-fetch-links xwidget #'xwwp-follow-link-callback)))
+
+;; Local Variables:
+;; eval: (mmm-mode)
+;; eval: (mmm-add-group 'elisp-js '((elisp-rawjs :submode js-mode
+;;                                               :face mmm-code-submode-face
+;;                                               :delimiter-mode nil
+;;                                               :front "xwwp--js \"" :back "\" js--")
+;;                                  (elisp-defjs :submode js-mode
+;;                                               :face mmm-code-submode-face
+;;                                               :delimiter-mode nil
+;;                                               :front "xwwp-js-def .*\n.*\"\"\n" :back "\")\n")))
+;; mmm-classes: elisp-js
+;; End:
+
+(provide 'xwwp-follow-link)
+;;; xwwp-follow-link.el ends here

+ 51 - 58
xwidget-plus-common.el → xwwp.el

@@ -1,13 +1,16 @@
-;;; xwidget-plus-common.el --- Helper functions for xwidget-plus. -*- lexical-binding: t; -*-
+;;; xwwp.el --- Enhance xwidget webkit browser -*- lexical-binding: t; -*-
+
+;; Author: Damien Merenne
+;; URL: https://github.com/canatella/xwwp
+;; Created: 2020-03-11
+;; Keywords: convenience
+;; Version: 0.1
+;; Package-Requires: ((emacs "26.1"))
 
 ;; Copyright (C) 2020 Damien Merenne <dam@cosinux.org>
 
 ;; This file is NOT part of GNU Emacs.
 
-;;; Commentary:
-
-;; Shared functions for the xwidget-plus package.
-
 ;;; License:
 
 ;; This program is free software: you can redistribute it and/or modify
@@ -25,37 +28,27 @@
 
 ;;; Commentary:
 
-;;
+;; This package provides the common functionnality for other xwidget webkit plus
+;; packages.  It provides the customize group and a framework to inject css and
+;; javascript functions into an `xwidget-webkit' session.
 
 ;;; Code:
 
-(defgroup xwidget-plus nil
-  "Augment the xwidget webkit browser."
+(defgroup xwwp nil
+  "`xwidget-webkit' browser enhancement suite."
   :group 'convenience)
 
-(defcustom xwidget-plus-completion-system 'default
-  "The completion system to be used by xwidget plus.
 
-Custom function should be a function that takes no arguments and
-returns an instance of an eieio class extending
-`xwidget-plus-completion-backend'."
-  :group 'xwidget-plus
-  :type '(radio
-          (const :tag "Ido" ido)
-          (const :tag "Helm" helm)
-          (const :tag "Ivy" ivy)
-          (const :tag "Default" default)
-          (function :tag "Custom function")))
 
 (require 'json)
 (require 'subr-x)
 (require 'xwidget)
 
-(defun xwidget-plus-make-class (class style)
+(defun xwwp-css-make-class (class style)
   "Generate a css CLASS definition from the STYLE alist."
   (format ".%s { %s }\\n" class (mapconcat (lambda (v) (format "%s: %s;" (car v) (cdr v))) style " ")))
 
-(defmacro xwidget-plus--js (js _ &rest replacements)
+(defmacro xwwp--js (js _ &rest replacements)
   "Apply `format' on JS with REPLACEMENTS  providing MMM mode delimiters.
 
 This file has basic support for javascript using MMM mode and
@@ -63,17 +56,17 @@ local variables (see at the end of the file)."
   (declare (indent 2))
   `(format ,js ,@replacements))
 
-(defun xwidget-plus-js-string-escape (string)
+(defun xwwp-js-string-escape (string)
   "Escape STRING for injection."
   (replace-regexp-in-string "\n" "\\\\n" (replace-regexp-in-string "'" "\\\\'" string)))
 
-(defun xwidget-plus-inject-head-element (xwidget tag id type content)
+(defun xwwp-html-inject-head-element (xwidget tag id type content)
   "Insert TAG element under XWIDGET head with ID TYPE and CONTENT."
-  (let* ((id (xwidget-plus-js-string-escape id))
-         (tag (xwidget-plus-js-string-escape tag))
-         (type (xwidget-plus-js-string-escape type))
-         (content (xwidget-plus-js-string-escape content))
-         (script (xwidget-plus--js "
+  (let* ((id (xwwp-js-string-escape id))
+         (tag (xwwp-js-string-escape tag))
+         (type (xwwp-js-string-escape type))
+         (content (xwwp-js-string-escape content))
+         (script (xwwp--js "
 __xwidget_id = '%s';
 if (!document.getElementById(__xwidget_id)) {
     var e = document.createElement('%s');
@@ -86,82 +79,82 @@ null;
 " js-- id tag type content)))
     (xwidget-webkit-execute-script xwidget script)))
 
-(defun xwidget-plus-inject-script (xwidget id script)
+(defun xwwp-html-inject-script (xwidget id script)
   "Inject javascript SCRIPT in XWIDGET session using a script element with ID."
-  (xwidget-plus-inject-head-element xwidget "script" id "text/javascript" script))
+  (xwwp-html-inject-head-element xwidget "script" id "text/javascript" script))
 
-(defun xwidget-plus-inject-style (xwidget id style)
+(defun xwwp-html-inject-style (xwidget id style)
   "Inject css STYLE in XWIDGET session using a style element with ID."
-  (xwidget-plus-inject-head-element xwidget "style" id "text/css" style))
+  (xwwp-html-inject-head-element xwidget "style" id "text/css" style))
 
-(defun xwidget-plus-lisp-to-js (identifier)
+(defun xwwp-js-lisp-to-js (identifier)
   "Convert IDENTIFIER from Lisp style to javascript style."
   (replace-regexp-in-string "-" "_" (if (symbolp identifier) (symbol-name identifier) identifier)))
 
-(defvar xwidget-plus-js-scripts '() "An  alist of list of javascript function.")
+(defvar xwwp-js-scripts '() "An  alist of list of javascript function.")
 
-(defun xwidget-plus-js-register-function (ns-name name js-script)
+(defun xwwp-js-register-function (ns-name name js-script)
   "Register javascript function NAME in namespace NS-NAME with body JS-SCRIPT."
-  (let* ((namespace (assoc ns-name xwidget-plus-js-scripts))
+  (let* ((namespace (assoc ns-name xwwp-js-scripts))
          (fun (when namespace (assoc name (cdr namespace)))))
     (cond (fun
            (delete fun namespace)
-           (xwidget-plus-js-register-function ns-name name js-script))
+           (xwwp-js-register-function ns-name name js-script))
           ((not namespace)
-           (push (cons ns-name '()) xwidget-plus-js-scripts)
-           (xwidget-plus-js-register-function ns-name name js-script))
+           (push (cons ns-name '()) xwwp-js-scripts)
+           (xwwp-js-register-function ns-name name js-script))
           (t
            (push (cons name js-script) (cdr namespace))))
     (cons ns-name name)))
 
-(defun xwidget-plus-js-funcall (xwidget namespace name &rest arguments)
+(defun xwwp-js-funcall (xwidget namespace name &rest arguments)
   "Invoke javascript function NAME in XWIDGET instance passing ARGUMENTS witch CALLBACK in NAMESPACE."
   ;;; Try to be smart
   (let* ((callback (car (last arguments)))
          (arguments (if (functionp callback) (reverse (cdr (reverse arguments))) arguments))
          (json-args (seq-map #'json-encode arguments))
          (arg-string (string-join json-args ", "))
-         (namespace (xwidget-plus-lisp-to-js namespace))
-         (name (xwidget-plus-lisp-to-js name))
+         (namespace (xwwp-js-lisp-to-js namespace))
+         (name (xwwp-js-lisp-to-js name))
          (script (format "__xwidget_plus_%s_%s(%s)" namespace name arg-string)))
     (xwidget-webkit-execute-script xwidget script (and (functionp callback) callback))))
 
-(defmacro xwidget-plus-js-def (namespace name arguments docstring js-body)
+(defmacro xwwp-js-def (namespace name arguments docstring js-body)
   "Create a function NAME with ARGUMENTS, DOCSTRING and JS-BODY.
 
 This will define a javascript function in the namespace NAMESPACE
 and a Lisp function to call it."
   (declare (indent 3) (doc-string 4))
-  (let* ((js-arguments (seq-map #'xwidget-plus-lisp-to-js arguments))
-         (js-name (xwidget-plus-lisp-to-js name))
-         (js-namespace (xwidget-plus-lisp-to-js namespace))
+  (let* ((js-arguments (seq-map #'xwwp-js-lisp-to-js arguments))
+         (js-name (xwwp-js-lisp-to-js name))
+         (js-namespace (xwwp-js-lisp-to-js namespace))
          (lisp-arguments (append '(xwidget) arguments '(&optional callback)))
-         (script (xwidget-plus--js "function __xwidget_plus_%s_%s(%s) {%s};" js--
+         (script (xwwp--js "function __xwidget_plus_%s_%s(%s) {%s};" js--
                    js-namespace js-name (string-join js-arguments ", ") (eval js-body)))
-         (lisp-def  `(defun ,(intern (format "xwidget-plus-%s-%s" namespace name)) ,lisp-arguments
+         (lisp-def  `(defun ,(intern (format "xwwp-%s-%s" namespace name)) ,lisp-arguments
                        ,docstring
-                       (xwidget-plus-js-funcall xwidget (quote ,namespace) (quote ,name) ,@arguments callback)))
-         (lisp-store `(xwidget-plus-js-register-function (quote ,namespace) (quote ,name) ,script)))
+                       (xwwp-js-funcall xwidget (quote ,namespace) (quote ,name) ,@arguments callback)))
+         (lisp-store `(xwwp-js-register-function (quote ,namespace) (quote ,name) ,script)))
     `(progn ,lisp-def ,lisp-store)))
 
-(defun xwidget-plus-js-inject (xwidget ns-name)
+(defun xwwp-js-inject (xwidget ns-name)
   "Inject the functions defined in NS-NAME into XWIDGET session."
-  (let* ((namespace (assoc ns-name xwidget-plus-js-scripts))
+  (let* ((namespace (assoc ns-name xwwp-js-scripts))
          (script (mapconcat #'cdr (cdr namespace) "\n")))
-    (xwidget-plus-inject-script xwidget (format "--xwidget-plus-%s" (symbol-name ns-name)) script)))
+    (xwwp-html-inject-script xwidget (format "--xwwp-%s" (symbol-name ns-name)) script)))
 
 ;; Local Variables:
 ;; eval: (mmm-mode)
 ;; eval: (mmm-add-group 'elisp-js '((elisp-rawjs :submode js-mode
 ;;                                               :face mmm-code-submode-face
 ;;                                               :delimiter-mode nil
-;;                                               :front "xwidget-plus--js \"" :back "\" js--")
+;;                                               :front "xwwp--js \"" :back "\" js--")
 ;;                                  (elisp-defjs :submode js-mode
 ;;                                               :face mmm-code-submode-face
 ;;                                               :delimiter-mode nil
-;;                                               :front "xwidget-plus-defjs .*\n.*\"\"\n" :back "\")\n")))
+;;                                               :front "xwwp-defjs .*\n.*\"\"\n" :back "\")\n")))
 ;; mmm-classes: elisp-js
 ;; End:
 
-(provide 'xwidget-plus-common)
-;;; xwidget-plus-common.el ends here
+(provide 'xwwp)
+;;; xwwp.el ends here