Coverage for src/pyselector/menus/rofi.py: 100%

80 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-03-24 11:53 -0300

1# menus.rofi.py 

2 

3import logging 

4import shlex 

5import sys 

6import warnings 

7from typing import Iterable 

8from typing import Optional 

9from typing import Union 

10 

11from pyselector import helpers 

12from pyselector.interfaces import KeyManager 

13from pyselector.interfaces import PromptReturn 

14 

15log = logging.getLogger(__name__) 

16 

17ROFI_RETURN_CODE_START = 10 

18BULLET = "\u2022" 

19 

20 

21class Rofi: 

22 """ 

23 A Python wrapper for the rofi application, which provides a simple and 

24 efficient way to display a list of items for user selection. 

25 

26 This class provides a convenient interface for building and executing rofi commands, 

27 allowing customization of various settings such as case sensitivity, multi-select, 

28 prompt message, and more 

29 

30 Methods: 

31 prompt(items=None, case_sensitive=False, multi_select=False, prompt="PySelector> ", **kwargs): 

32 Displays a rofi selection window with the specified items and settings, 

33 returns the selected item(s) and return code. 

34 """ 

35 

36 def __init__(self) -> None: 

37 self.name = "rofi" 

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

39 self.keybind = KeyManager() 

40 

41 self.keybind.code_count = ROFI_RETURN_CODE_START 

42 

43 @property 

44 def command(self) -> str: 

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

46 

47 def _build_command( 

48 self, 

49 case_sensitive, 

50 multi_select, 

51 prompt, 

52 **kwargs, 

53 ) -> list[str]: 

54 messages: list[str] = [] 

55 dimensions_args: list[str] = [] 

56 args = [] 

57 

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

59 args.append("-dmenu") 

60 

61 if kwargs.get("theme"): 

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

63 

64 if kwargs.get("lines"): 

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

66 

67 if prompt: 

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

69 

70 if kwargs.get("mesg"): 

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

72 

73 if kwargs.get("filter"): 

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

75 

76 if kwargs.get("location"): 

77 direction = kwargs.pop("location") 

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

79 

80 if kwargs.get("width"): 

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

82 

83 if kwargs.get("height"): 

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

85 

86 if case_sensitive: 

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

88 else: 

89 args.append("-i") 

90 

91 if multi_select: 

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

93 

94 if dimensions_args: 

95 formated_string = " ".join(dimensions_args) 

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

97 

98 for key in self.keybind.list_registered: 

99 args.extend(shlex.split(f"-kb-custom-{key.id} {key.bind}")) 

100 if not key.hidden: 

101 messages.append(f"{BULLET} Use <{key.bind}> {key.description}") 

102 

103 if messages: 

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

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

106 

107 if kwargs: 

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

109 warnings.warn(UserWarning(f"'{arg}={value}' not supported")) 

110 

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

112 return args 

113 

114 def prompt( 

115 self, 

116 items: Optional[Iterable[Union[str, int]]] = None, 

117 case_sensitive: bool = False, 

118 multi_select: bool = False, 

119 prompt: str = "PySelector> ", 

120 **kwargs, 

121 ) -> PromptReturn: 

122 """Prompts the user with a rofi window containing the given items 

123 and returns the selected item and code. 

124 

125 Args: 

126 items (Iterable[str, int], optional): The items to display in the rofi window 

127 case_sensitive (bool, optional): Whether or not to perform a case-sensitive search 

128 multi_select (bool, optional): Whether or not to allow the user to select multiple items 

129 prompt (str, optional): The prompt to display in the rofi window 

130 **kwargs: Additional keyword arguments. 

131 

132 Keyword Args: 

133 lines (int): The number of lines to display in the selection window. 

134 mesg (str): A message to display at the top of the selection window. 

135 filter (str): Filter the list by setting text in input bar to filter. 

136 location (str): The location of the selection window (e.g. "upper-left", "center" or "bottom-right"). 

137 width (str): The width of the selection window (e.g. 60%). 

138 height (str): The height of the selection window (e.g. 50%). 

139 theme (str): The path of the rofi theme to use. 

140 

141 Returns: 

142 A tuple containing the selected item (str or list of str if `multi_select` activated) and the return code (int). 

143 """ 

144 if items is None: 

145 items = [] 

146 

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

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

149 

150 if multi_select: 

151 return helpers.parse_multiple_bytes_lines(selection), code 

152 return helpers.parse_bytes_line(selection), code 

153 

154 @staticmethod 

155 def location(direction: Optional[str] = None) -> str: 

156 """ 

157 Specify where the window should be located. The numbers map to the 

158 following locations on screen: 

159 

160 1 2 3 

161 8 0 4 

162 7 6 5 

163 

164 Default: 0 

165 """ 

166 try: 

167 location = { 

168 "upper-left": 1, 

169 "left": 8, 

170 "bottom-left": 7, 

171 "upper-center": 2, 

172 "center": 0, 

173 "bottom-center": 6, 

174 "upper-right": 3, 

175 "right": 4, 

176 "bottom-right": 5, 

177 } 

178 return str(location[direction]) 

179 except KeyError as e: 

180 raise KeyError( 

181 "location %s not found.\nchosse from %s", e, list(location.keys()) 

182 ) from e 

183 sys.exit(1)