Compare commits
3 Commits
482d443314
...
8bfc4a5900
Author | SHA1 | Date | |
---|---|---|---|
8bfc4a5900 | |||
b33da2a881 | |||
35267cfa34 |
10
README.md
10
README.md
@ -3,7 +3,7 @@
|
||||
This is a Python rewrite of Gilles Castel's [Instant Reference](https://github.com/gillescastel/instant-reference) tool.
|
||||
(I was not a fan of needing NPM rather than the system package manager to install some dependencies.)
|
||||
|
||||
pyinstantref allows you to copy a link to a specific page in a PDF with a single keybind in Zathura.
|
||||
pyinstantref allows you to copy a link to a specific page or header in a PDF with a single keybind in Zathura.
|
||||
You can then paste this reference in your notes and other documents.
|
||||
|
||||
For now, it only works with my own [templates](https://github.com/dogeystamp/typst-templates) for [Typst](https://github.com/typst/typst),
|
||||
@ -50,7 +50,7 @@ This will make Ctrl-L copy a reference to the current page in Zathura.
|
||||
|
||||
## limitations
|
||||
|
||||
Currently, the following features are missing:
|
||||
Currently, the following features are missing compared to Castel's version:
|
||||
- ArXiv support
|
||||
- LaTeX output
|
||||
- Support for other PDF readers (e.g. Evince)
|
||||
@ -58,3 +58,9 @@ Currently, the following features are missing:
|
||||
Feel free to send pull requests,
|
||||
although this project is primarily for my own usage
|
||||
and I can not make any guarantees.
|
||||
|
||||
Also:
|
||||
- Section references are unreliable because titles might change,
|
||||
and there might be sections with the same title.
|
||||
Proper IDs for bookmarks are possible,
|
||||
but not until Typst resolves [issue #1352](https://github.com/typst/typst/issues/1352).
|
||||
|
24
copy_ref
24
copy_ref
@ -1,10 +1,10 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from pdf_data import get_page_pdf
|
||||
from pdf_data import get_page_pdf, get_section_pdf
|
||||
from datatypes import *
|
||||
from enum import Enum, auto
|
||||
from util import notify
|
||||
import subprocess
|
||||
import pydbus
|
||||
import argparse
|
||||
import formatter.typst as typst_fmt
|
||||
|
||||
@ -32,21 +32,19 @@ def copy_ref(ref: Reference, format: LinkFormat) -> None:
|
||||
clip_copy(link_txt)
|
||||
|
||||
|
||||
def notify(title:str, txt: str) -> None:
|
||||
"""Send a text notification."""
|
||||
bus = pydbus.SessionBus()
|
||||
notifs = bus.get(".Notifications")
|
||||
notifs.Notify("instantref", 0, "dialog-information", title, txt, [], {}, 5000)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.section:
|
||||
raise NotImplementedError("--section isn't implemented")
|
||||
|
||||
ref = get_page_pdf()
|
||||
ref = get_section_pdf()
|
||||
else:
|
||||
ref = get_page_pdf()
|
||||
|
||||
format = LinkFormat.TYPST
|
||||
copy_ref(ref, format)
|
||||
notify("Copied ref", f"{ref.filepath.name} p. {ref.page}")
|
||||
|
||||
match ref:
|
||||
case PDFPage():
|
||||
notify("Copied ref", f"{ref.filepath.name} p. {ref.page}")
|
||||
case PDFSection():
|
||||
notify("Copied ref", f"{ref.filepath.name} sec. {ref.title}")
|
||||
|
@ -11,6 +11,7 @@ ProcessId = NewType("ProcessId", int)
|
||||
@dataclass
|
||||
class _Reference:
|
||||
"""Reference to a location within a file."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@ -63,3 +64,11 @@ PDFReference = Union[PDFPage, PDFSection]
|
||||
# for now no other format is implemented
|
||||
# replace this with an union if that happens
|
||||
Reference = PDFReference
|
||||
|
||||
|
||||
# PyMuPDF type
|
||||
@dataclass
|
||||
class FitzBookmark:
|
||||
level: int
|
||||
title: SectionTitle
|
||||
page: PageNumber
|
||||
|
@ -4,6 +4,7 @@ from datatypes import PDFPage, PDFSection, PDFReference, Reference
|
||||
from typing import assert_never
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def format_pdf_link(ref: PDFReference) -> str:
|
||||
path_str = environ.get("TYPST_ROOT", None)
|
||||
if path_str is None:
|
||||
@ -19,19 +20,24 @@ def format_pdf_link(ref: PDFReference) -> str:
|
||||
format_path = str(ref.filepath.absolute())
|
||||
|
||||
params = {}
|
||||
default_label = ""
|
||||
|
||||
match ref:
|
||||
case PDFPage():
|
||||
params["page"] = ref.page
|
||||
case PDFSection():
|
||||
params["section"] = ref.title
|
||||
default_label = ref.title
|
||||
case _ as obj:
|
||||
assert_never(obj)
|
||||
|
||||
if relative:
|
||||
return f'#lref("{format_path}?{urlencode(params)}", pdfref: true)[]'
|
||||
return (
|
||||
f'#lref("{format_path}?{urlencode(params)}", pdfref: true)[{default_label}]'
|
||||
)
|
||||
else:
|
||||
return f'#link("pdfref://{format_path}?{urlencode(params)}")[]'
|
||||
return f'#link("pdfref://{format_path}?{urlencode(params)}")[{default_label}]'
|
||||
|
||||
|
||||
def ref(ref: Reference) -> str:
|
||||
"""Formats a Reference."""
|
||||
|
17
pdf_data.py
17
pdf_data.py
@ -1,7 +1,24 @@
|
||||
from pathlib import Path
|
||||
from datatypes import *
|
||||
from typing import cast, Any
|
||||
from util import rofi
|
||||
import pydbus
|
||||
import subprocess
|
||||
import fitz
|
||||
|
||||
|
||||
def get_section_pdf() -> PDFSection:
|
||||
page_ref: PDFPage = get_page_pdf()
|
||||
with fitz.Document(page_ref.filepath) as doc:
|
||||
toc = [FitzBookmark(*x) for x in cast(Any, doc).get_toc()]
|
||||
page_headers = [x for x in toc if x.page == page_ref.page]
|
||||
|
||||
rofi_res = rofi([f"{x.title}" for x in page_headers], prompt="Select header: ")
|
||||
if rofi_res is None or rofi_res.index is None:
|
||||
raise RuntimeError("No header was selected.")
|
||||
selected_header = page_headers[rofi_res.index]
|
||||
|
||||
return PDFSection(filepath=page_ref.filepath, title=selected_header.title)
|
||||
|
||||
|
||||
def get_page_pdf() -> PDFPage:
|
||||
|
@ -1,15 +1,31 @@
|
||||
#!/usr/bin/env python3
|
||||
# pdfref:// URL handler
|
||||
|
||||
import subprocess
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
from sys import argv
|
||||
|
||||
from datatypes import PageNumber
|
||||
from datatypes import *
|
||||
from typing import cast, Any
|
||||
from util import notify
|
||||
import subprocess
|
||||
import fitz
|
||||
|
||||
url = urlparse(argv[1])
|
||||
query = parse_qs(url.query)
|
||||
|
||||
page: PageNumber = PageNumber(int(query.get("page", ["0"])[0]))
|
||||
section: SectionTitle = SectionTitle(query.get("section", [])[0])
|
||||
|
||||
if section != []:
|
||||
with fitz.Document(url.path) as doc:
|
||||
toc = [FitzBookmark(*x) for x in cast(Any, doc).get_toc()]
|
||||
headers = [x for x in toc if x.title == section]
|
||||
if headers == []:
|
||||
notify("", f"Failed to find section '{section}': did the title change?")
|
||||
else:
|
||||
if len(headers) > 1:
|
||||
notify("", f"Multiple sections '{section}' found: page might be incorrect")
|
||||
|
||||
page = headers[0].page
|
||||
|
||||
|
||||
subprocess.run(["zathura", "--page", str(page), url.path], text=True)
|
||||
|
@ -1,3 +1,4 @@
|
||||
pycairo==1.24.0
|
||||
pydbus==0.6.0
|
||||
PyGObject==3.44.1
|
||||
PyMuPDF==1.22.5
|
||||
|
18
util.py
18
util.py
@ -1,7 +1,9 @@
|
||||
import subprocess
|
||||
import pydbus
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class RofiResult:
|
||||
"""Data returned from Rofi.
|
||||
@ -17,12 +19,15 @@ class RofiResult:
|
||||
custom_bind
|
||||
ID of custom bind used to select entry. None if no custom bind was used.
|
||||
"""
|
||||
|
||||
index: Optional[int]
|
||||
value: str
|
||||
custom_bind: Optional[int]
|
||||
|
||||
|
||||
def rofi(entries: list[str], prompt: str="> ", fuzzy=True, extra_args=[]) -> Optional[RofiResult]:
|
||||
def rofi(
|
||||
entries: list[str], prompt: str = "> ", fuzzy=True, extra_args=[]
|
||||
) -> Optional[RofiResult]:
|
||||
"""Start a Rofi prompt.
|
||||
|
||||
Returns
|
||||
@ -39,7 +44,9 @@ def rofi(entries: list[str], prompt: str="> ", fuzzy=True, extra_args=[]) -> Opt
|
||||
|
||||
ret = RofiResult(None, "", None)
|
||||
|
||||
res = subprocess.run(args, input="\0".join(entries), stdout=subprocess.PIPE, text=True)
|
||||
res = subprocess.run(
|
||||
args, input="\0".join(entries), stdout=subprocess.PIPE, text=True
|
||||
)
|
||||
match res.returncode:
|
||||
case 0:
|
||||
pass
|
||||
@ -56,3 +63,10 @@ def rofi(entries: list[str], prompt: str="> ", fuzzy=True, extra_args=[]) -> Opt
|
||||
pass
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def notify(title: str, txt: str) -> None:
|
||||
"""Send a text notification."""
|
||||
bus = pydbus.SessionBus()
|
||||
notifs = bus.get(".Notifications")
|
||||
notifs.Notify("instantref", 0, "dialog-information", title, txt, [], {}, 5000)
|
||||
|
Loading…
x
Reference in New Issue
Block a user