소스 검색

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)