Coverage for src/pyselector/menus/rofi.py: 96%
80 statements
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-29 12:33 -0300
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-29 12:33 -0300
1# menus.rofi.py
2from __future__ import annotations
4import logging
5import shlex
6import sys
7from typing import TYPE_CHECKING
8from typing import Iterable
9from typing import Optional
10from typing import Union
12from pyselector import helpers
13from pyselector.key_manager import KeyManager
15if TYPE_CHECKING:
16 from pyselector.interfaces import PromptReturn
19log = logging.getLogger(__name__)
21ROFI_RETURN_CODE_START = 10
22BULLET = "\u2022"
25class Rofi:
26 """
27 A Python wrapper for the rofi application, which provides a simple and
28 efficient way to display a list of items for user selection.
30 This class provides a convenient interface for building and executing rofi commands,
31 allowing customization of various settings such as case sensitivity, multi-select,
32 prompt message, and more
34 Methods:
35 prompt(items=None, case_sensitive=False, multi_select=False, prompt="PySelector> ", **kwargs):
36 Displays a rofi selection window with the specified items and settings,
37 returns the selected item(s) and return code.
38 """
40 def __init__(self) -> None:
41 self.name = "rofi"
42 self.url = "https://github.com/davatorium/rofi"
43 self.keybind = KeyManager()
45 self.keybind.code_count = ROFI_RETURN_CODE_START
47 @property
48 def command(self) -> str:
49 return helpers.check_command(self.name, self.url)
51 def _build_command(
52 self,
53 case_sensitive,
54 multi_select,
55 prompt,
56 **kwargs,
57 ) -> list[str]:
58 messages: list[str] = []
59 dimensions_args: list[str] = []
60 args = []
62 args.extend(shlex.split(self.command))
63 args.append("-dmenu")
65 if kwargs.get("theme"):
66 args.extend(["-theme", kwargs.pop("theme")])
68 if kwargs.get("lines"):
69 args.extend(["-l", str(kwargs.pop("lines"))])
71 if prompt:
72 args.extend(["-p", prompt])
74 if kwargs.get("mesg"):
75 messages.extend(shlex.split(f"'{kwargs.pop('mesg')}'"))
77 if kwargs.get("filter"):
78 args.extend(["-filter", kwargs.pop("filter")])
80 if kwargs.get("location"):
81 direction = kwargs.pop("location")
82 args.extend(["-location", self.location(direction)])
84 if kwargs.get("width"):
85 dimensions_args.append(f"width: {kwargs.pop('width')};")
87 if kwargs.get("height"):
88 dimensions_args.append(f"height: {kwargs.pop('height')};")
90 if case_sensitive:
91 args.append("-case-sensitive")
92 else:
93 args.append("-i")
95 if multi_select:
96 args.append("-multi-select")
98 if dimensions_args:
99 formated_string = " ".join(dimensions_args)
100 args.extend(shlex.split("-theme-str 'window {" + formated_string + "}'"))
102 for key in self.keybind.all_registered:
103 args.extend(shlex.split(f"-kb-custom-{key.id} {key.bind}"))
104 if not key.hidden:
105 messages.append(f"{BULLET} Use <{key.bind}> {key.description}")
107 if messages:
108 mesg = "\n".join(messages)
109 args.extend(shlex.split(f"-mesg '{mesg}'"))
111 if kwargs:
112 for arg, value in kwargs.items():
113 log.debug("'%s=%s' not supported", arg, value)
115 args.extend(shlex.split("-theme-str 'textbox { markup: false;}'"))
116 return args
118 def prompt(
119 self,
120 items: Optional[Iterable[Union[str, int]]] = None,
121 case_sensitive: bool = False,
122 multi_select: bool = False,
123 prompt: str = "PySelector> ",
124 **kwargs,
125 ) -> PromptReturn:
126 """Prompts the user with a rofi window containing the given items
127 and returns the selected item and code.
129 Args:
130 items (Iterable[str, int], optional): The items to display in the rofi window
131 case_sensitive (bool, optional): Whether or not to perform a case-sensitive search
132 multi_select (bool, optional): Whether or not to allow the user to select multiple items
133 prompt (str, optional): The prompt to display in the rofi window
134 **kwargs: Additional keyword arguments.
136 Keyword Args:
137 lines (int): The number of lines to display in the selection window.
138 mesg (str): A message to display at the top of the selection window.
139 filter (str): Filter the list by setting text in input bar to filter.
140 location (str): The location of the selection window (e.g. "upper-left", "center" or "bottom-right").
141 width (str): The width of the selection window (e.g. 60%).
142 height (str): The height of the selection window (e.g. 50%).
143 theme (str): The path of the rofi theme to use.
145 Returns:
146 A tuple containing the selected item (str or list of str if `multi_select` activated) and the return code (int).
147 """
148 if items is None:
149 items = []
151 args = self._build_command(case_sensitive, multi_select, prompt, **kwargs)
152 selection, code = helpers._execute(args, items)
154 if multi_select:
155 return helpers.parse_multiple_bytes_lines(selection), code
156 return helpers.parse_bytes_line(selection), code
158 @staticmethod
159 def location(direction: Optional[str] = None) -> str:
160 """
161 Specify where the window should be located. The numbers map to the
162 following locations on screen:
164 1 2 3
165 8 0 4
166 7 6 5
168 Default: 0
169 """
170 try:
171 location = {
172 "upper-left": 1,
173 "left": 8,
174 "bottom-left": 7,
175 "upper-center": 2,
176 "center": 0,
177 "bottom-center": 6,
178 "upper-right": 3,
179 "right": 4,
180 "bottom-right": 5,
181 }
182 return str(location[direction])
183 except KeyError as e:
184 raise KeyError(
185 "location %s not found.\nchosse from %s", e, list(location.keys())
186 ) from e
187 sys.exit(1)