From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp11.migadu.com ([2001:41d0:2:4a6f::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by ms5.migadu.com with LMTPS id uGedBoVQE2OTKgEAbAwnHQ (envelope-from ) for ; Sat, 03 Sep 2022 15:03:01 +0200 Received: from aspmx1.migadu.com ([2001:41d0:2:4a6f::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp11.migadu.com with LMTPS id wPSoBoVQE2OJTAEA9RJhRA (envelope-from ) for ; Sat, 03 Sep 2022 15:03:01 +0200 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by aspmx1.migadu.com (Postfix) with ESMTPS id B7C5E3EAD for ; Sat, 3 Sep 2022 15:03:00 +0200 (CEST) Received: from localhost ([::1]:45448 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1oUSna-0002TI-RU for larch@yhetil.org; Sat, 03 Sep 2022 09:02:58 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:48084) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1oUSlm-0002S3-24 for emacs-orgmode@gnu.org; Sat, 03 Sep 2022 09:01:09 -0400 Received: from ciao.gmane.io ([116.202.254.214]:42750) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1oUSlf-0002Qh-GT for emacs-orgmode@gnu.org; Sat, 03 Sep 2022 09:01:04 -0400 Received: from list by ciao.gmane.io with local (Exim 4.92) (envelope-from ) id 1oUSlb-000AdU-Pg for emacs-orgmode@gnu.org; Sat, 03 Sep 2022 15:00:55 +0200 X-Injected-Via-Gmane: http://gmane.org/ To: emacs-orgmode@gnu.org From: Max Nikulin Subject: Re: Org mode links: Open a PDF file at a given page and highlight a given string Date: Sat, 3 Sep 2022 20:00:47 +0700 Message-ID: References: <87lfb5pbej.fsf@gmail.com> Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 7bit User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Thunderbird/91.11.0 Content-Language: en-US In-Reply-To: <87lfb5pbej.fsf@gmail.com> Received-SPF: pass client-ip=116.202.254.214; envelope-from=geo-emacs-orgmode@m.gmane-mx.org; helo=ciao.gmane.io X-Spam_score_int: 28 X-Spam_score: 2.8 X-Spam_bar: ++ X-Spam_report: (2.8 / 5.0 requ) BAYES_00=-1.9, DKIM_ADSP_CUSTOM_MED=0.001, FORGED_GMAIL_RCVD=1, FORGED_MUA_MOZILLA=2.309, FREEMAIL_FORGED_FROMDOMAIN=0.249, FREEMAIL_FROM=0.001, FUZZY_CPILL=0.001, HEADER_FROM_DIFFERENT_DOMAINS=0.25, NICE_REPLY_A=-0.001, NML_ADSP_CUSTOM_MED=0.9, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, T_SCC_BODY_TEXT_LINE=-0.01 autolearn=no autolearn_force=no X-Spam_action: no action X-BeenThere: emacs-orgmode@gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: "General discussions about Org-mode." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-orgmode-bounces+larch=yhetil.org@gnu.org Sender: "Emacs-orgmode" X-Migadu-Flow: FLOW_IN X-Migadu-To: larch@yhetil.org X-Migadu-Country: US ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=yhetil.org; s=key1; t=1662210180; h=from:from:sender:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:mime-version:mime-version: content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references:list-id:list-help: list-unsubscribe:list-subscribe:list-post; bh=MhF1IgxLO7zcdb6QEHcjzQLUWDYQYUATGW4of8Su2KY=; b=Z1KDsswt07S4jIqaRCJnn7VHBwxAj5UYFapakR2vuo808NROwGFyLAGeIJZkk4dBK47dlg pBHgl7KlyQzTziTt8bGHwGu/svOYEw3qCyah/KSNJ4/CB2ITr4wgLa+SyFfruJh3FXC3XW GsdCL3fJ5M5Xr/g568djgOM9ScLGPEe7HPWlw9NzpFc3BXZ6oANmvulMxYIN4jkWMaGFuD dOFgbggcRR+vZwrJ8rJarPJ9NWrZov8ROu1UKpKgcZoIpApExdcOU5/8kSUjaN+0JYPzNB SXP1A/P76rQrYx/NmwtOAC0La8nr1EeZCk6/DfQ8XD8dx8Rx8S498BOGYFAHZQ== ARC-Seal: i=1; s=key1; d=yhetil.org; t=1662210180; a=rsa-sha256; cv=none; b=jWF6S2c5HtgaWRfTHW3D2yyXrNCSMxXRqgnk6pT29u671St6A5f6OGb/G2cmGnsniZt7A5 PAdz3+kBmG5YLtdNYl58UK1c4wpYGJAgh9EyvQo+JBIwO27Cu3CwtUPnq0pHMa+PlkBAfa JCTyGr2JSDDM+EIX7DKeR/GFEhJljopywzTYkj2ne+klopCeO+U15l4AiFrGF9VsazNuob ugK6KxWzFauYR1Kwm3EaVDU90zXEU2VTnZDxOMJoNzE5mvrmc6CJlZHDJOUIb8HZQAp4IU WgOg4m1yBI8AClmiGJvxRUvpJH1uR2X1YihuZNfW7ijYhNLoL4jQV5OYJlCEQw== ARC-Authentication-Results: i=1; aspmx1.migadu.com; dkim=none; dmarc=fail reason="SPF not aligned (relaxed), No valid DKIM" header.from=gmail.com (policy=none); spf=pass (aspmx1.migadu.com: domain of "emacs-orgmode-bounces+larch=yhetil.org@gnu.org" designates 209.51.188.17 as permitted sender) smtp.mailfrom="emacs-orgmode-bounces+larch=yhetil.org@gnu.org" X-Migadu-Spam-Score: 2.94 Authentication-Results: aspmx1.migadu.com; dkim=none; dmarc=fail reason="SPF not aligned (relaxed), No valid DKIM" header.from=gmail.com (policy=none); spf=pass (aspmx1.migadu.com: domain of "emacs-orgmode-bounces+larch=yhetil.org@gnu.org" designates 209.51.188.17 as permitted sender) smtp.mailfrom="emacs-orgmode-bounces+larch=yhetil.org@gnu.org" X-Migadu-Queue-Id: B7C5E3EAD X-Spam-Score: 2.94 X-Migadu-Scanner: scn1.migadu.com X-TUID: +n0fSENdYuUF On 03/03/2021 03:07, Rodrigo Morales wrote: > > #+begin_src emacs-lisp :results silent > (setq org-file-apps > '(("\\.pdf::\\([0-9]+\\)::\\([^:]+\\)\\'" . "zathura -P %1 -f %2 %s"))) > #+end_src I am going to respond to a message from another thread containing discussion of a patch, but I suppose the following considerations are more appropriate in the thread discussing combined specifier for location withing a PDF document. For me it is more convenient to test ideas using okular, I hope, changing code for zathura or other PDF viewer is trivial. Ihor Radchenko. Re: [PATCH] org.el: Fix percent substitutions in `org-open-file' Fri, 02 Sep 2022 20:08:17 +0800. https://list.orgmode.org/87tu5qm11q.fsf@localhost >> + ;; Page and search string, >> + ;; e.g. . >> + (\"\\\\.pdf::\\\\([0-9]+\\\\)::\\\\(.+\\\\)\\\\\\='\" >> + . \"okular --page %1 --find %2 %s\") >> + ;; Internal anchor and search string, >> + ;; e.g. . >> + (\"\\\\.pdf::\\\\(.+\\\\)::\\\\(.+\\\\)\\\\\\='\" >> + . \"okular --find %2 file://%s\\\\\\\\#%1\") >> + ;; Page number, e.g. . >> + (\"\\\\.pdf::\\\\([0-9]+\\\\)\\\\\\='\" . \"okular --page %1 %s\") >> + ;; Internal reference, e.g. . >> + (\"\\\\.pdf::\\\\(.+\\\\)\\\\\\='\" . \"okular file://%s\\\\\\\\#%1\") >> + ;; No location within the file, optionally followed by \"::\", >> + ;; e.g. . >> + (\"\\\\.pdf\\\\(?:::\\\\)?\\\\\\='\" . \"okular %s\") > > This is a nice set of examples, but it probably does not belong to this > docstring. I'd rather see this in `org-file-apps' docstring or even in > the manual. It is a part of docstring, so number of slashes is doubled. First of all, I overlooked possibility to distinguish text search "file:text.pdf::patttern" and cross reference target within the document "file:text.pdf::#anchor". Secondly, I forgot that PDF viewers may support compressed files. Currently I believe that instead of injecting up to 6 entries into `org-file-apps' for various combinations of page, anchor, and search pattern, it is better to add single record with function handler. Notice that the approach presented above is not affected by the bug with multiple regexp group. Its additional advantage is that shell is not involved, so peculiar file names can not cause execution of some code when quoting and escaping are messed up. I think a set of functions for popular PDF viewers (evince, zathura, okular, xpdf, xpopple, firefox, chromium & Co., etc.) should be defined in some package, but I am in doubts if it suitable for Org core. Proof of concept implementation. Configuration: (add-to-list 'org-file-apps `(("\\.pdf\\(?:\\.gz\\|\\.bz2\\|\\.xz\\)?\\(?:::.*\\)?\\'" . ,#'my-open-file-pdf-okular))) Helper functions: (defun my--parse-file-link-search (suffix-re link) "Parse PDF file LINK for page number, cross reference anchor, search string. Return nil if it is not a link to some PDF file. Any element may be nil." (let ((case-fold-search t)) ; Handle files having .PDF suffix as well (and (string-match (concat suffix-re (rx (optional "::" (or (group (+ digit)) (group "#" (+ (not (any ?:)))) (optional "#"))) (optional "::" (optional (group (+ anything)))) string-end)) link) (mapcar (lambda (i) (match-string i link)) '(1 2 3))))) (defun my-launch-viewer (command arguments) "Launch external application COMMAND with ARGUMENTS. The function allows to avoid intermediate shell and so escaping of arguments that otherwise might be considered as shell specialls and run arbitrary commands. The function launches viewer process using shoot ant forget method like `browse-url-xdg-open', so the application may run even after quit from Emacs." (apply #'call-process command nil 0 nil args)) (defun my-open-file-pdf-okular (file link) "PDF files handler for usage as a command in `org-file-apps' alist. Customize `org-file-apps' to add the following entry: \\='(\"\\\\.pdf\\\\(?:\\\\.gz\\\\|\\\\.bz2\\\\|\\\\.xz\\\\)?\\\\(?:::.*\\\\)?\\\\\\='\" . #\\='my-open-file-pdf-okular) Open FILE at the location specified by LINK (page, internal reference, search string). Supported link search options (side note: in the particular case of bash manual link may be used instead): - Page number . - Page number and search text . - Cross reference anchor . - Cross reference anchor and search text . - Search text . Optionally the FILE may be compressed by gzip, bzip2, or xz." (pcase-let* ((pdf-re (rx ".pdf" ;; .Z and .zip are not supported by okular (optional (or ".gz" ".bz2" ".xz")))) (`(,page ,ref ,find) (or (my--parse-file-link-search pdf-re link) (error "Not a PDF file link: %S" link))) (args (list "--" (if (org-string-nw-p ref) (concat file ref) file)))) ;; Protect against file names starting from a dash that might be ;; considered as an option despite `org-open-file' passes absolute ;; file name and it is not strictly necessary. (when find (push find args) (push "--find" args)) (when page (push page args) (push "--page" args)) (my-launch-viewer "okular" args))) And some tests (ert-deftest test-my/parse-file-link-search () (let ((pdf-re (rx ".pdf" ;; .Z and .zip are not supported by okular (optional (or ".gz" ".bz2" ".xz"))))) (should-not (my--parse-file-link-search pdf-re "/no-match.doc")) (should-not (my--parse-file-link-search pdf-re "/no-match.doc::#ref")) (should (equal '(nil nil nil) (my--parse-file-link-search pdf-re "/just-file.pdf"))) (should (equal '(nil nil nil) (my--parse-file-link-search pdf-re "/just-file-upper-case.PDF"))) (should (equal '("21" nil nil) (my--parse-file-link-search pdf-re "/page.pdf::21"))) (should (equal '(nil "#ref" nil) (my--parse-file-link-search pdf-re "/anchor.pdf::#ref"))) (should (equal '(nil nil "some text") (my--parse-file-link-search pdf-re "/search-string.pdf::some text"))) (should (equal '(nil nil "in gzipped file") (my--parse-file-link-search pdf-re "/compressed-search-string.pdf.GZ::in gzipped file"))) (should (equal '("32" nil "page text") (my--parse-file-link-search pdf-re "/page-search.pdf::32::page text"))) (should (equal '(nil "#ref" "anchor text") (my--parse-file-link-search pdf-re "/anchor-search.pdf::#ref::anchor text"))) (should (equal '(nil nil "::") (my--parse-file-link-search pdf-re "/search-quad.pdf::::::"))) (should (equal '(nil nil nil) (my--parse-file-link-search pdf-re "/nothing-1.pdf::"))) (should (equal '(nil nil nil) (my--parse-file-link-search pdf-re "/nothing-2.PDF::::"))) (should (equal '(nil nil nil) (my--parse-file-link-search pdf-re "/empty-anchor-1.pdf::#"))) (should (equal '(nil nil nil) (my--parse-file-link-search pdf-re "/empty-anchor-2.pdf::#::"))) (should (equal '(nil nil "empty anchor text") (my--parse-file-link-search pdf-re "/empty-anchor-1.pdf::#::empty anchor text")))))