Преглед на файлове

add improvements for ivy backend to support user defined actions that access the
URLs of links and for fancier display with ivy rich. Thanks to Q. Hong who
extended xxwp-follow-link to store urls for candidates.

Boris Glavic преди 5 години
родител
ревизия
99670ec37e
променени са 4 файла, в които са добавени 92 реда и са изтрити 31 реда
  1. 14 16
      test/xwwp-follow-link-test.el
  2. 1 1
      xwwp-follow-link-helm.el
  3. 56 1
      xwwp-follow-link-ivy.el
  4. 21 13
      xwwp-follow-link.el

+ 14 - 16
test/xwwp-follow-link-test.el

@@ -108,20 +108,20 @@ return '' + window.location;
        ,@body)))
 
 (ert-deftest test-xwwp-follow-link-prepare-links ()
-  (let ((links '(("3" . "Functions")
-                 ("1" . "Function Cells")
-                 ("12" . "Structures")
-                 ("2" . "Anonymous Functions")
-                 ("9" . "Declare Form"))))
-    (should (equal '(("Function Cells" . 1)
-	             ("Anonymous Functions" . 2)
-	             ("Functions" . 3)
-	             ("Declare Form" . 9)
-	             ("Structures" . 12))
+  (let ((links '(("3" . ["Functions" "http://www.this.is.a.test.de/functions"])
+                 ("1" . ["Function Cells" "http://www.this.is.a.test.de/function-cells"])
+                 ("12" . ["Structures" "http://www.this.is.a.test.de/structures"])
+                 ("2" . ["Anonymous Functions" "http://www.this.is.a.test.de/anon"])
+                 ("9" . ["Declare Form" "http://www.this.is.a.test.de/declare-form"]))))
+    (should (equal '(("Function Cells" . (1 "http://www.this.is.a.test.de/function-cells"))
+	             ("Anonymous Functions" . (2 "http://www.this.is.a.test.de/anon"))
+	             ("Functions" . (3 "http://www.this.is.a.test.de/functions"))
+	             ("Declare Form" . (9 "http://www.this.is.a.test.de/declare-form"))
+	             ("Structures" . (12 "http://www.this.is.a.test.de/structures")))
             (xwwp-follow-link-prepare-links links)))))
 
 (ert-deftest test-xwwp-follow-link-highlight ()
-  (with-test-backend-browse '(0 0 1) 0 "links.html"
+  (with-test-backend-browse '((0 "http://") (0 "http://") (1 "http://")) '(0 "http://") "links.html"
     (xwwp-follow-link)
     (xwwp-event-loop)
     (let ((backend xwwp-follow-link-completion-backend-instance))
@@ -131,7 +131,7 @@ return '' + window.location;
       (should (string= "test-1.html" (file-name-nondirectory (oref backend location))))
       (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"
+  (with-test-backend-browse '((1 "http://") (0 "http://") (1 "http://")) '(1 "http://") "links.html"
     (xwwp-follow-link)
     (xwwp-event-loop)
     (let ((backend xwwp-follow-link-completion-backend-instance))
@@ -140,10 +140,8 @@ return '' + window.location;
       (xwwp-event-dispatch)
       (should (string= "test-2.html" (file-name-nondirectory (oref backend location))))
       (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-xwwp-follow-link-highlight-no-candidates ()
-  (with-test-backend-browse '(1) 1 "links.html"
+      (should (equal (backend-test-link-classes backend "test-2") '["xwwp-follow-link-selected"]))))
+  (with-test-backend-browse '((1 "http://")) '(1 "http://") "links.html"
     (xwwp-follow-link)
     (xwwp-event-loop)
     (let ((backend xwwp-follow-link-completion-backend-instance))

+ 1 - 1
xwwp-follow-link-helm.el

@@ -7,7 +7,7 @@
 ;; Version: 0.1
 ;; Package-Requires: ((emacs "26.1") (xwwp "0.1"))
 
-;; Copyright (C) 2020 Damien Merenne <dam@cosinux.org>
+;; Copyright (C) 2020 Q. Hong <qhong@mit.edu>, Damien Merenne <dam@cosinux.org>
 
 ;; This file is NOT part of GNU Emacs.
 

+ 56 - 1
xwwp-follow-link-ivy.el

@@ -36,9 +36,14 @@
 (require 'xwwp-follow-link)
 (require 'ivy)
 
+(defvar xwwp-follow-link-ivy-history
+  nil
+  "Stores history for links selected with command `xwwp-follow-link-completion-backend-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))
+  "Follow a link selected with ivy."
   (with-current-buffer (ivy-state-buffer ivy-last)
     (let* ((collection (ivy-state-collection ivy-last))
            (current (ivy-state-current ivy-last))
@@ -47,7 +52,57 @@
       (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))
+  "Select a link from COLLECTION a with ivy showing PROMPT using UPDATE-FN to highlight the link in the xwidget session and execute ACTION once a link has been select."
+  (ivy-read prompt collection
+            :require-match t
+            :action (lambda (v) (funcall action (cdr v)))
+            :update-fn update-fn
+            :caller 'xwwp-follow-link-read
+            :history 'xwwp-follow-link-ivy-history))
+
+(defmacro xwwp-follow-link-ivy-ivify-action (v &rest body)
+  "Wraps BODY as an action for the `xwwp-follow-link-read' `ivy' version.
+The text and url of the link is made available through variables `linktext' and
+`linkurl' extracted from the selection candidate V.  Before the action is
+executed, the link highlights are removed from the HTML document shown in
+xwidgets."
+  `(let ((xwidget (xwidget-webkit-current-session))
+         (linktext (car ,v))
+         (linkurl (caddr ,v)))
+    (xwwp-follow-link-cleanup xwidget)
+    ,body))
+
+(defun xwwp-follow-link-ivy-copy-url-action (v)
+  "Copy the selected url from candidate V to the `kill-ring'."
+  (xwwp-follow-link-ivy-ivify-action v
+   (ignore 'linktext)
+   (kill-new linkurl)))
+
+(defun xwwp-follow-link-ivy-browse-external-action (v)
+  "Open the selected url from candidate V in the default browser."
+  (xwwp-follow-link-ivy-ivify-action v
+   (ignore 'linktext)
+   (browse-url linkurl)))
+
+(defun xwwp-follow-link-ivy-get-full-candidate (c)
+  "Return the full candidate for a text candidate C from xwwp-follow-link-ivy."
+  (with-current-buffer (ivy-state-buffer ivy-last)
+    (let* ((collection (ivy-state-collection ivy-last)))
+      (nth (get-text-property 0 'idx c) collection))))
+
+(defun xwwp-follow-link-ivy-get-candidate-text (v)
+  "Return the link text for completion candiate V for displaying candidates in `ivy-rich'."
+  (substring-no-properties (car (xwwp-follow-link-ivy-get-full-candidate v))))
+
+(defun xwwp-follow-link-ivy-get-candidate-url (v)
+  "Return the url for for a complettion candidate V for displaying candidates in `ivy-rich'."
+  (caddr (xwwp-follow-link-ivy-get-full-candidate v)))
+
+;; add ivy actions
+(ivy-add-actions
+ 'xwwp-follow-link-read
+ '(("w" xwwp-follow-link-ivy-copy-url-action "Copy URL")
+   ("e" xwwp-follow-link-ivy-browse-external-action "Open in default browser")))
 
 (provide 'xwwp-follow-link-ivy)
 ;;; xwwp-follow-link-ivy.el ends here

+ 21 - 13
xwwp-follow-link.el

@@ -1,6 +1,6 @@
 ;;; xwwp-follow-link.el --- Link navigation in `xwidget-webkit' sessions -*- lexical-binding: t; -*-
 
-;; Copyright (C) 2020 Damien Merenne <dam@cosinux.org>
+;; Copyright (C) 2020 Damien Merenne <dam@cosinux.org>, Q. Hong <qhong@mit.edu>
 
 ;; This file is NOT part of GNU Emacs.
 
@@ -69,14 +69,15 @@ returns an instance of an eieio class extending
 
 (xwwp-js-def follow-link cleanup ()
   "Remove all custom class from links.""
-document.querySelectorAll('a').forEach(a => {
+window.__xwidget_plus_follow_link_candidates.forEach(a => {
     a.classList.remove('xwwp-follow-link-candidate', 'xwwp-follow-link-selected');
 });
+window.__xwidget_plus_follow_link_candidates = null;
 ")
 
 (xwwp-js-def follow-link highlight (ids selected)
   "Highlight IDS as candidate and SELECTED as selected.""
-document.querySelectorAll('a').forEach((a, id) => {
+window.__xwidget_plus_follow_link_candidates.forEach((a, id) => {
     a.classList.remove('xwwp-follow-link-candidate', 'xwwp-follow-link-selected');
     if (selected == id) {
         a.classList.add('xwwp-follow-link-selected');
@@ -89,17 +90,19 @@ document.querySelectorAll('a').forEach((a, id) => {
 
 (xwwp-js-def follow-link action (link-id)
   "Click on the link identified by LINK-ID""
+let selected = window.__xwidget_plus_follow_link_candidates[link_id];
 __xwidget_plus_follow_link_cleanup();
-document.querySelectorAll('a')[link_id].click();
+selected.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) => {
+window.__xwidget_plus_follow_link_candidates = Array.from(document.querySelectorAll('a'));
+window.__xwidget_plus_follow_link_candidates.forEach((a, i) => {
     if (a.offsetWidth || a.offsetHeight || a.getClientRects().length) {
         if (a.innerText.match(/\\\\S/))
-            r[i] = a.innerText;
+            r[i] = [a.innerText, a.href];
     }
 });
 return r;
@@ -188,16 +191,18 @@ browser."
 (defvar xwwp-follow-link-completion-backend-instance '())
 
 (defun xwwp-follow-link-update (xwidget)
-  "Highligh LINKS in XWIDGET buffer when updating candidates."
+  "Highlight 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)))))
+        (xwwp-follow-link-highlight xwidget (mapcar 'car candidates) (car selected))))))
 
 (defun xwwp-follow-link-trigger-action (xwidget selected)
-  "Activate link matching SELECTED in XWIDGET LINKS."
-  (xwwp-follow-link-action xwidget selected))
+  "Activate link matching SELECTED in XWIDGET LINKS.
+The SELECTED value is the cdr of an assoc in the collection passed to
+completion back end, which is of the form (numerical-id link-url)"
+  (xwwp-follow-link-action xwidget (car selected)))
 
 (defun xwwp-follow-link-format-link (str)
   "Format link title STR."
@@ -208,12 +213,15 @@ browser."
 
 (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))))
+  (seq-sort-by (lambda (v) (cadr v)) #'<
+               (seq-map (lambda (v) (list (xwwp-follow-link-format-link (aref (cdr v) 0))
+                                          (string-to-number (car v))
+                                          (aref (cdr v) 1)))
                         links)))
 
 (defun xwwp-follow-link-callback (links)
-  "Ask for a link belonging to the alist LINKS."
+  "Ask for a link belonging to the alist LINKS.
+LINKS maps a numerical ID to an array of form [link-text, link-uri]"
   (let* ((xwidget (xwidget-webkit-current-session))
          (links (xwwp-follow-link-prepare-links links)))
     (oset xwwp-follow-link-completion-backend-instance collection links)