diff options
author | Alejandro Gallo <aamsgallo@gmail.com> | 2021-06-12 03:23:15 +0200 |
---|---|---|
committer | Alejandro Gallo <aamsgallo@gmail.com> | 2021-06-12 03:23:15 +0200 |
commit | d1de7ecb26b7f15572f1007edd3ded2adf1d3c56 (patch) | |
tree | 99a3f8d3b9d8614da56739f76079d97d7274b06a | |
parent | 68071b072b5453a698f45e7ff73ae86ac5f49156 (diff) |
Add fzf picker
-rw-r--r-- | README.rst | 1 | ||||
-rw-r--r-- | papis/config.py | 10 | ||||
-rw-r--r-- | papis/fzf.py | 111 | ||||
-rw-r--r-- | setup.cfg | 3 | ||||
-rw-r--r-- | setup.py | 1 |
5 files changed, 125 insertions, 1 deletions
@@ -11,7 +11,6 @@ Papis |Packaging_status| |zulip| -..for libera: libera-vg8HTHf8ki94NEbBeoBYyfyj Description ----------- diff --git a/papis/config.py b/papis/config.py index 778da110..b61ee996 100644 --- a/papis/config.py +++ b/papis/config.py @@ -113,6 +113,16 @@ general_settings = { "document-description-format": '{doc[title]} - {doc[author]}', "formater": "python", + + # fzf options + "fzf-binary": "fzf", + "fzf-extra-flags": ["--ansi", "--multi", "-i"], + "fzf-extra-bindings": [], + "fzf-header-format": ("\x1b[35m{doc[title]:<70.70}\x1b[0m" + " :: " + "\x1b[36m{doc[author]:<20.20}\x1b[0m " + "\x1b[33m«{doc[year]:4}»\x1b[0m " + ":{doc[tags]}") } diff --git a/papis/fzf.py b/papis/fzf.py new file mode 100644 index 00000000..51496a13 --- /dev/null +++ b/papis/fzf.py @@ -0,0 +1,111 @@ +from typing import Callable, Sequence, TypeVar, List, Optional, Generic, Union +from typing.re import Pattern +from abc import ABC, abstractmethod +import subprocess as sp +import re + +import papis.pick +import papis.config +import papis.format + +T = TypeVar("T") + + +class Command(ABC, Generic[T]): + regex = None # type: Optional[Pattern] + command = "" # type: str + key = "" # type: str + + def binding(self) -> str: + return "{0}:{1}".format(self.key, self.command) + + def indices(self, line: str) -> Optional[List[int]]: + m = self.regex.match(line) if self.regex else None + return [int(i) for i in m.group(1).split()] if m else None + + @abstractmethod + def run(self, docs: Sequence[T]) -> Sequence[T]: + ... + + +class Choose(Command[T]): + regex = re.compile(r"choose ([\d ]+)") + command = "execute(echo choose {+n})+accept" + key = "enter" + + def run(self, docs: Sequence[T]) -> Sequence[T]: + return docs + + +class Edit(Command[T]): + regex = re.compile(r"edit ([\d ]+)") + command = "execute(echo edit {+n})" + key = "ctrl-e" + + def run(self, docs: Sequence[T]) -> Sequence[T]: + from papis.commands.edit import run + for doc in docs: + if isinstance(doc, papis.document.Document): + run(doc) + return [] + + +class Open(Command[T]): + regex = re.compile(r"open ([\d ]+)") + command = "execute(echo open {+n})" + key = "ctrl-o" + + def run(self, docs: Sequence[T]) -> Sequence[T]: + from papis.commands.open import run + for doc in docs: + if isinstance(doc, papis.document.Document): + run(doc) + return [] + + +class Picker(papis.pick.Picker[T]): + def __call__(self, + options: Sequence[T], + header_filter: Callable[[T], str] = str, + match_filter: Callable[[T], str] = str, + default_index: int = 0) -> Sequence[T]: + + if len(options) == 0: + return [] + if len(options) == 1: + return [options[0]] + + commands = [Choose(), Open(), Edit()] # type: Sequence[Command[T]] + + bindings = [c.binding() for c in commands + ] + papis.config.getlist("fzf-extra-bindings") + + command = [papis.config.getstring("fzf-binary"), + "--bind", ",".join(bindings) + ] + papis.config.getlist("fzf-extra-flags") + + _fmt = papis.config.getstring("fzf-header-format") + "\n" + + def _header_filter(d: T) -> str: + if isinstance(d, papis.document.Document): + return papis.format.format(_fmt, d) + else: + return str(d) + + headers = [_header_filter(o) for o in options] + docs = [] # type: Sequence[T] + + with sp.Popen(command, stdin=sp.PIPE, stdout=sp.PIPE) as p: + if p.stdin is not None: + with p.stdin as stdin: + for h in headers: + stdin.write(h.encode()) + if p.stdout is not None: + for line in p.stdout.readlines(): + for c in commands: + indices = c.indices(line.decode()) + if not indices: + continue + docs = c.run([options[i] for i in indices]) + + return docs @@ -14,6 +14,9 @@ addopts = --doctest-modules norecursedirs = .git doc build dist python_files = *.py +[mypy-typing.re.*] +ignore_missing_imports = True + [mypy-whoosh.*] ignore_missing_imports = True @@ -156,6 +156,7 @@ setup( ], 'papis.picker': [ 'papis=papis.tui.picker:Picker', + 'fzf=papis.fzf:Picker', ], 'papis.format': [ 'python=papis.format:PythonFormater', |