Compare commits

..

No commits in common. "8bfc4a590032097ca354ff4a3421c12721d7f00b" and "482d4433142b1149497a4f02b32c939be1fb5e5b" have entirely different histories.

8 changed files with 23 additions and 90 deletions

View File

@ -3,7 +3,7 @@
This is a Python rewrite of Gilles Castel's [Instant Reference](https://github.com/gillescastel/instant-reference) tool. 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.) (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 or header in a PDF with a single keybind in Zathura. pyinstantref allows you to copy a link to a specific page in a PDF with a single keybind in Zathura.
You can then paste this reference in your notes and other documents. 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), 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 ## limitations
Currently, the following features are missing compared to Castel's version: Currently, the following features are missing:
- ArXiv support - ArXiv support
- LaTeX output - LaTeX output
- Support for other PDF readers (e.g. Evince) - Support for other PDF readers (e.g. Evince)
@ -58,9 +58,3 @@ Currently, the following features are missing compared to Castel's version:
Feel free to send pull requests, Feel free to send pull requests,
although this project is primarily for my own usage although this project is primarily for my own usage
and I can not make any guarantees. 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).

View File

@ -1,10 +1,10 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from pdf_data import get_page_pdf, get_section_pdf from pdf_data import get_page_pdf
from datatypes import * from datatypes import *
from enum import Enum, auto from enum import Enum, auto
from util import notify
import subprocess import subprocess
import pydbus
import argparse import argparse
import formatter.typst as typst_fmt import formatter.typst as typst_fmt
@ -32,19 +32,21 @@ def copy_ref(ref: Reference, format: LinkFormat) -> None:
clip_copy(link_txt) 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__": if __name__ == "__main__":
args = parser.parse_args() args = parser.parse_args()
if args.section: if args.section:
ref = get_section_pdf() raise NotImplementedError("--section isn't implemented")
else:
ref = get_page_pdf() ref = get_page_pdf()
format = LinkFormat.TYPST format = LinkFormat.TYPST
copy_ref(ref, format) 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}")

View File

@ -11,7 +11,6 @@ ProcessId = NewType("ProcessId", int)
@dataclass @dataclass
class _Reference: class _Reference:
"""Reference to a location within a file.""" """Reference to a location within a file."""
pass pass
@ -64,11 +63,3 @@ PDFReference = Union[PDFPage, PDFSection]
# for now no other format is implemented # for now no other format is implemented
# replace this with an union if that happens # replace this with an union if that happens
Reference = PDFReference Reference = PDFReference
# PyMuPDF type
@dataclass
class FitzBookmark:
level: int
title: SectionTitle
page: PageNumber

View File

@ -4,7 +4,6 @@ from datatypes import PDFPage, PDFSection, PDFReference, Reference
from typing import assert_never from typing import assert_never
from pathlib import Path from pathlib import Path
def format_pdf_link(ref: PDFReference) -> str: def format_pdf_link(ref: PDFReference) -> str:
path_str = environ.get("TYPST_ROOT", None) path_str = environ.get("TYPST_ROOT", None)
if path_str is None: if path_str is None:
@ -20,24 +19,19 @@ def format_pdf_link(ref: PDFReference) -> str:
format_path = str(ref.filepath.absolute()) format_path = str(ref.filepath.absolute())
params = {} params = {}
default_label = ""
match ref: match ref:
case PDFPage(): case PDFPage():
params["page"] = ref.page params["page"] = ref.page
case PDFSection(): case PDFSection():
params["section"] = ref.title params["section"] = ref.title
default_label = ref.title
case _ as obj: case _ as obj:
assert_never(obj) assert_never(obj)
if relative: if relative:
return ( return f'#lref("{format_path}?{urlencode(params)}", pdfref: true)[]'
f'#lref("{format_path}?{urlencode(params)}", pdfref: true)[{default_label}]'
)
else: else:
return f'#link("pdfref://{format_path}?{urlencode(params)}")[{default_label}]' return f'#link("pdfref://{format_path}?{urlencode(params)}")[]'
def ref(ref: Reference) -> str: def ref(ref: Reference) -> str:
"""Formats a Reference.""" """Formats a Reference."""

View File

@ -1,24 +1,7 @@
from pathlib import Path from pathlib import Path
from datatypes import * from datatypes import *
from typing import cast, Any
from util import rofi
import pydbus import pydbus
import subprocess 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: def get_page_pdf() -> PDFPage:

View File

@ -1,31 +1,15 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# pdfref:// URL handler # pdfref:// URL handler
import subprocess
from urllib.parse import urlparse, parse_qs from urllib.parse import urlparse, parse_qs
from sys import argv from sys import argv
from datatypes import *
from typing import cast, Any from datatypes import PageNumber
from util import notify
import subprocess
import fitz
url = urlparse(argv[1]) url = urlparse(argv[1])
query = parse_qs(url.query) query = parse_qs(url.query)
page: PageNumber = PageNumber(int(query.get("page", ["0"])[0])) 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) subprocess.run(["zathura", "--page", str(page), url.path], text=True)

View File

@ -1,4 +1,3 @@
pycairo==1.24.0 pycairo==1.24.0
pydbus==0.6.0 pydbus==0.6.0
PyGObject==3.44.1 PyGObject==3.44.1
PyMuPDF==1.22.5

20
util.py
View File

@ -1,9 +1,7 @@
import subprocess import subprocess
import pydbus
from dataclasses import dataclass from dataclasses import dataclass
from typing import Optional from typing import Optional
@dataclass @dataclass
class RofiResult: class RofiResult:
"""Data returned from Rofi. """Data returned from Rofi.
@ -19,15 +17,12 @@ class RofiResult:
custom_bind custom_bind
ID of custom bind used to select entry. None if no custom bind was used. ID of custom bind used to select entry. None if no custom bind was used.
""" """
index: Optional[int] index: Optional[int]
value: str value: str
custom_bind: Optional[int] custom_bind: Optional[int]
def rofi( def rofi(entries: list[str], prompt: str="> ", fuzzy=True, extra_args=[]) -> Optional[RofiResult]:
entries: list[str], prompt: str = "> ", fuzzy=True, extra_args=[]
) -> Optional[RofiResult]:
"""Start a Rofi prompt. """Start a Rofi prompt.
Returns Returns
@ -43,10 +38,8 @@ def rofi(
args += extra_args args += extra_args
ret = RofiResult(None, "", None) ret = RofiResult(None, "", None)
res = subprocess.run( res = subprocess.run(args, input="\0".join(entries), stdout=subprocess.PIPE, text=True)
args, input="\0".join(entries), stdout=subprocess.PIPE, text=True
)
match res.returncode: match res.returncode:
case 0: case 0:
pass pass
@ -63,10 +56,3 @@ def rofi(
pass pass
return ret 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)