summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlejandro Gallo <aamsgallo@gmail.com>2021-06-12 03:23:15 +0200
committerAlejandro Gallo <aamsgallo@gmail.com>2021-06-12 03:23:15 +0200
commitd1de7ecb26b7f15572f1007edd3ded2adf1d3c56 (patch)
tree99a3f8d3b9d8614da56739f76079d97d7274b06a
parent68071b072b5453a698f45e7ff73ae86ac5f49156 (diff)
Add fzf picker
-rw-r--r--README.rst1
-rw-r--r--papis/config.py10
-rw-r--r--papis/fzf.py111
-rw-r--r--setup.cfg3
-rw-r--r--setup.py1
5 files changed, 125 insertions, 1 deletions
diff --git a/README.rst b/README.rst
index f049868a..32fed276 100644
--- a/README.rst
+++ b/README.rst
@@ -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
diff --git a/setup.cfg b/setup.cfg
index 39fc65b1..9cae0a23 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -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
diff --git a/setup.py b/setup.py
index 54b5d867..7b5afc2f 100644
--- a/setup.py
+++ b/setup.py
@@ -156,6 +156,7 @@ setup(
],
'papis.picker': [
'papis=papis.tui.picker:Picker',
+ 'fzf=papis.fzf:Picker',
],
'papis.format': [
'python=papis.format:PythonFormater',