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

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

88 statements  

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

8 

9from __future__ import annotations 

10 

11from enum import Enum 

12from typing import TYPE_CHECKING 

13 

14from .application.current import get_app 

15from .filters import FilterOrBool, is_searching, to_filter 

16from .key_binding.vi_state import InputMode 

17 

18if TYPE_CHECKING: 

19 from prompt_toolkit.layout.controls import BufferControl, SearchBufferControl 

20 from prompt_toolkit.layout.layout import Layout 

21 

22__all__ = [ 

23 "SearchDirection", 

24 "start_search", 

25 "stop_search", 

26] 

27 

28 

29class SearchDirection(Enum): 

30 FORWARD = "FORWARD" 

31 BACKWARD = "BACKWARD" 

32 

33 

34class SearchState: 

35 """ 

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

37 

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

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

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

41 search query. 

42 

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

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

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

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

47 search control). 

48 """ 

49 

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

51 

52 def __init__( 

53 self, 

54 text: str = "", 

55 direction: SearchDirection = SearchDirection.FORWARD, 

56 ignore_case: FilterOrBool = False, 

57 ) -> None: 

58 self.text = text 

59 self.direction = direction 

60 self.ignore_case = to_filter(ignore_case) 

61 

62 def __repr__(self) -> str: 

63 return f"{self.__class__.__name__}({self.text!r}, direction={self.direction!r}, ignore_case={self.ignore_case!r})" 

64 

65 def __invert__(self) -> SearchState: 

66 """ 

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

68 way around. 

69 """ 

70 if self.direction == SearchDirection.BACKWARD: 

71 direction = SearchDirection.FORWARD 

72 else: 

73 direction = SearchDirection.BACKWARD 

74 

75 return SearchState( 

76 text=self.text, direction=direction, ignore_case=self.ignore_case 

77 ) 

78 

79 

80def start_search( 

81 buffer_control: BufferControl | None = None, 

82 direction: SearchDirection = SearchDirection.FORWARD, 

83) -> None: 

84 """ 

85 Start search through the given `buffer_control` using the 

86 `search_buffer_control`. 

87 

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

89 search through the current control. 

90 """ 

91 from prompt_toolkit.layout.controls import BufferControl 

92 

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

94 

95 layout = get_app().layout 

96 

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

98 if buffer_control is None: 

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

100 return 

101 buffer_control = layout.current_control 

102 

103 # Only if this control is searchable. 

104 search_buffer_control = buffer_control.search_buffer_control 

105 

106 if search_buffer_control: 

107 buffer_control.search_state.direction = direction 

108 

109 # Make sure to focus the search BufferControl 

110 layout.focus(search_buffer_control) 

111 

112 # Remember search link. 

113 layout.search_links[search_buffer_control] = buffer_control 

114 

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

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

117 

118 

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

120 """ 

121 Stop search through the given `buffer_control`. 

122 """ 

123 layout = get_app().layout 

124 

125 if buffer_control is None: 

126 buffer_control = layout.search_target_buffer_control 

127 if buffer_control is None: 

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

129 # when we're not searching.) 

130 return 

131 search_buffer_control = buffer_control.search_buffer_control 

132 else: 

133 assert buffer_control in layout.search_links.values() 

134 search_buffer_control = _get_reverse_search_links(layout)[buffer_control] 

135 

136 # Focus the original buffer again. 

137 layout.focus(buffer_control) 

138 

139 if search_buffer_control is not None: 

140 # Remove the search link. 

141 del layout.search_links[search_buffer_control] 

142 

143 # Reset content of search control. 

144 search_buffer_control.buffer.reset() 

145 

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

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

148 

149 

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

151 """ 

152 Apply search, but keep search buffer focused. 

153 """ 

154 assert is_searching() 

155 

156 layout = get_app().layout 

157 

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

159 from prompt_toolkit.layout.controls import BufferControl 

160 

161 search_control = layout.current_control 

162 if not isinstance(search_control, BufferControl): 

163 return 

164 

165 prev_control = layout.search_target_buffer_control 

166 if prev_control is None: 

167 return 

168 search_state = prev_control.search_state 

169 

170 # Update search_state. 

171 direction_changed = search_state.direction != direction 

172 

173 search_state.text = search_control.buffer.text 

174 search_state.direction = direction 

175 

176 # Apply search to current buffer. 

177 if not direction_changed: 

178 prev_control.buffer.apply_search( 

179 search_state, include_current_position=False, count=count 

180 ) 

181 

182 

183def accept_search() -> None: 

184 """ 

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

186 """ 

187 layout = get_app().layout 

188 

189 search_control = layout.current_control 

190 target_buffer_control = layout.search_target_buffer_control 

191 

192 from prompt_toolkit.layout.controls import BufferControl 

193 

194 if not isinstance(search_control, BufferControl): 

195 return 

196 if target_buffer_control is None: 

197 return 

198 

199 search_state = target_buffer_control.search_state 

200 

201 # Update search state. 

202 if search_control.buffer.text: 

203 search_state.text = search_control.buffer.text 

204 

205 # Apply search. 

206 target_buffer_control.buffer.apply_search( 

207 search_state, include_current_position=True 

208 ) 

209 

210 # Add query to history of search line. 

211 search_control.buffer.append_to_history() 

212 

213 # Stop search and focus previous control again. 

214 stop_search(target_buffer_control) 

215 

216 

217def _get_reverse_search_links( 

218 layout: Layout, 

219) -> dict[BufferControl, SearchBufferControl]: 

220 """ 

221 Return mapping from BufferControl to SearchBufferControl. 

222 """ 

223 return { 

224 buffer_control: search_buffer_control 

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

226 }