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

1# menus.rofi.py 

2from __future__ import annotations 

3 

4import logging 

5import shlex 

6import sys 

7from typing import TYPE_CHECKING 

8from typing import Iterable 

9from typing import Optional 

10from typing import Union 

11 

12from pyselector import helpers 

13from pyselector.key_manager import KeyManager 

14 

15if TYPE_CHECKING: 

16 from pyselector.interfaces import PromptReturn 

17 

18 

19log = logging.getLogger(__name__) 

20 

21ROFI_RETURN_CODE_START = 10 

22BULLET = "\u2022" 

23 

24 

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. 

29 

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 

33 

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 """ 

39 

40 def __init__(self) -> None: 

41 self.name = "rofi" 

42 self.url = "https://github.com/davatorium/rofi" 

43 self.keybind = KeyManager() 

44 

45 self.keybind.code_count = ROFI_RETURN_CODE_START 

46 

47 @property 

48 def command(self) -> str: 

49 return helpers.check_command(self.name, self.url) 

50 

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 = [] 

61 

62 args.extend(shlex.split(self.command)) 

63 args.append("-dmenu") 

64 

65 if kwargs.get("theme"): 

66 args.extend(["-theme", kwargs.pop("theme")]) 

67 

68 if kwargs.get("lines"): 

69 args.extend(["-l", str(kwargs.pop("lines"))]) 

70 

71 if prompt: 

72 args.extend(["-p", prompt]) 

73 

74 if kwargs.get("mesg"): 

75 messages.extend(shlex.split(f"'{kwargs.pop('mesg')}'")) 

76 

77 if kwargs.get("filter"): 

78 args.extend(["-filter", kwargs.pop("filter")]) 

79 

80 if kwargs.get("location"): 

81 direction = kwargs.pop("location") 

82 args.extend(["-location", self.location(direction)]) 

83 

84 if kwargs.get("width"): 

85 dimensions_args.append(f"width: {kwargs.pop('width')};") 

86 

87 if kwargs.get("height"): 

88 dimensions_args.append(f"height: {kwargs.pop('height')};") 

89 

90 if case_sensitive: 

91 args.append("-case-sensitive") 

92 else: 

93 args.append("-i") 

94 

95 if multi_select: 

96 args.append("-multi-select") 

97 

98 if dimensions_args: 

99 formated_string = " ".join(dimensions_args) 

100 args.extend(shlex.split("-theme-str 'window {" + formated_string + "}'")) 

101 

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}") 

106 

107 if messages: 

108 mesg = "\n".join(messages) 

109 args.extend(shlex.split(f"-mesg '{mesg}'")) 

110 

111 if kwargs: 

112 for arg, value in kwargs.items(): 

113 log.debug("'%s=%s' not supported", arg, value) 

114 

115 args.extend(shlex.split("-theme-str 'textbox { markup: false;}'")) 

116 return args 

117 

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. 

128 

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. 

135 

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. 

144 

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 = [] 

150 

151 args = self._build_command(case_sensitive, multi_select, prompt, **kwargs) 

152 selection, code = helpers._execute(args, items) 

153 

154 if multi_select: 

155 return helpers.parse_multiple_bytes_lines(selection), code 

156 return helpers.parse_bytes_line(selection), code 

157 

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: 

163 

164 1 2 3 

165 8 0 4 

166 7 6 5 

167 

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)