Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/prompt_toolkit/search.py: 24%

87 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-20 06:09 +0000

1""" 

2Search operations. 

3 

4For the key bindings implementation with attached filters, check 

5`prompt_toolkit.key_binding.bindings.search`. (Use these for new key bindings 

6instead of calling these function directly.) 

7""" 

8from __future__ import annotations 

9 

10from enum import Enum 

11from typing import TYPE_CHECKING 

12 

13from .application.current import get_app 

14from .filters import FilterOrBool, is_searching, to_filter 

15from .key_binding.vi_state import InputMode 

16 

17if TYPE_CHECKING: 

18 from prompt_toolkit.layout.controls import BufferControl, SearchBufferControl 

19 from prompt_toolkit.layout.layout import Layout 

20 

21__all__ = [ 

22 "SearchDirection", 

23 "start_search", 

24 "stop_search", 

25] 

26 

27 

28class SearchDirection(Enum): 

29 FORWARD = "FORWARD" 

30 BACKWARD = "BACKWARD" 

31 

32 

33class SearchState: 

34 """ 

35 A search 'query', associated with a search field (like a SearchToolbar). 

36 

37 Every searchable `BufferControl` points to a `search_buffer_control` 

38 (another `BufferControls`) which represents the search field. The 

39 `SearchState` attached to that search field is used for storing the current 

40 search query. 

41 

42 It is possible to have one searchfield for multiple `BufferControls`. In 

43 that case, they'll share the same `SearchState`. 

44 If there are multiple `BufferControls` that display the same `Buffer`, then 

45 they can have a different `SearchState` each (if they have a different 

46 search control). 

47 """ 

48 

49 __slots__ = ("text", "direction", "ignore_case") 

50 

51 def __init__( 

52 self, 

53 text: str = "", 

54 direction: SearchDirection = SearchDirection.FORWARD, 

55 ignore_case: FilterOrBool = False, 

56 ) -> None: 

57 self.text = text 

58 self.direction = direction 

59 self.ignore_case = to_filter(ignore_case) 

60 

61 def __repr__(self) -> str: 

62 return "{}({!r}, direction={!r}, ignore_case={!r})".format( 

63 self.__class__.__name__, 

64 self.text, 

65 self.direction, 

66 self.ignore_case, 

67 ) 

68 

69 def __invert__(self) -> SearchState: 

70 """ 

71 Create a new SearchState where backwards becomes forwards and the other 

72 way around. 

73 """ 

74 if self.direction == SearchDirection.BACKWARD: 

75 direction = SearchDirection.FORWARD 

76 else: 

77 direction = SearchDirection.BACKWARD 

78 

79 return SearchState( 

80 text=self.text, direction=direction, ignore_case=self.ignore_case 

81 ) 

82 

83 

84def start_search( 

85 buffer_control: BufferControl | None = None, 

86 direction: SearchDirection = SearchDirection.FORWARD, 

87) -> None: 

88 """ 

89 Start search through the given `buffer_control` using the 

90 `search_buffer_control`. 

91 

92 :param buffer_control: Start search for this `BufferControl`. If not given, 

93 search through the current control. 

94 """ 

95 from prompt_toolkit.layout.controls import BufferControl 

96 

97 assert buffer_control is None or isinstance(buffer_control, BufferControl) 

98 

99 layout = get_app().layout 

100 

101 # When no control is given, use the current control if that's a BufferControl. 

102 if buffer_control is None: 

103 if not isinstance(layout.current_control, BufferControl): 

104 return 

105 buffer_control = layout.current_control 

106 

107 # Only if this control is searchable. 

108 search_buffer_control = buffer_control.search_buffer_control 

109 

110 if search_buffer_control: 

111 buffer_control.search_state.direction = direction 

112 

113 # Make sure to focus the search BufferControl 

114 layout.focus(search_buffer_control) 

115 

116 # Remember search link. 

117 layout.search_links[search_buffer_control] = buffer_control 

118 

119 # If we're in Vi mode, make sure to go into insert mode. 

120 get_app().vi_state.input_mode = InputMode.INSERT 

121 

122 

123def stop_search(buffer_control: BufferControl | None = None) -> None: 

124 """ 

125 Stop search through the given `buffer_control`. 

126 """ 

127 layout = get_app().layout 

128 

129 if buffer_control is None: 

130 buffer_control = layout.search_target_buffer_control 

131 if buffer_control is None: 

132 # (Should not happen, but possible when `stop_search` is called 

133 # when we're not searching.) 

134 return 

135 search_buffer_control = buffer_control.search_buffer_control 

136 else: 

137 assert buffer_control in layout.search_links.values() 

138 search_buffer_control = _get_reverse_search_links(layout)[buffer_control] 

139 

140 # Focus the original buffer again. 

141 layout.focus(buffer_control) 

142 

143 if search_buffer_control is not None: 

144 # Remove the search link. 

145 del layout.search_links[search_buffer_control] 

146 

147 # Reset content of search control. 

148 search_buffer_control.buffer.reset() 

149 

150 # If we're in Vi mode, go back to navigation mode. 

151 get_app().vi_state.input_mode = InputMode.NAVIGATION 

152 

153 

154def do_incremental_search(direction: SearchDirection, count: int = 1) -> None: 

155 """ 

156 Apply search, but keep search buffer focused. 

157 """ 

158 assert is_searching() 

159 

160 layout = get_app().layout 

161 

162 # Only search if the current control is a `BufferControl`. 

163 from prompt_toolkit.layout.controls import BufferControl 

164 

165 search_control = layout.current_control 

166 if not isinstance(search_control, BufferControl): 

167 return 

168 

169 prev_control = layout.search_target_buffer_control 

170 if prev_control is None: 

171 return 

172 search_state = prev_control.search_state 

173 

174 # Update search_state. 

175 direction_changed = search_state.direction != direction 

176 

177 search_state.text = search_control.buffer.text 

178 search_state.direction = direction 

179 

180 # Apply search to current buffer. 

181 if not direction_changed: 

182 prev_control.buffer.apply_search( 

183 search_state, include_current_position=False, count=count 

184 ) 

185 

186 

187def accept_search() -> None: 

188 """ 

189 Accept current search query. Focus original `BufferControl` again. 

190 """ 

191 layout = get_app().layout 

192 

193 search_control = layout.current_control 

194 target_buffer_control = layout.search_target_buffer_control 

195 

196 from prompt_toolkit.layout.controls import BufferControl 

197 

198 if not isinstance(search_control, BufferControl): 

199 return 

200 if target_buffer_control is None: 

201 return 

202 

203 search_state = target_buffer_control.search_state 

204 

205 # Update search state. 

206 if search_control.buffer.text: 

207 search_state.text = search_control.buffer.text 

208 

209 # Apply search. 

210 target_buffer_control.buffer.apply_search( 

211 search_state, include_current_position=True 

212 ) 

213 

214 # Add query to history of search line. 

215 search_control.buffer.append_to_history() 

216 

217 # Stop search and focus previous control again. 

218 stop_search(target_buffer_control) 

219 

220 

221def _get_reverse_search_links( 

222 layout: Layout, 

223) -> dict[BufferControl, SearchBufferControl]: 

224 """ 

225 Return mapping from BufferControl to SearchBufferControl. 

226 """ 

227 return { 

228 buffer_control: search_buffer_control 

229 for search_buffer_control, buffer_control in layout.search_links.items() 

230 }