scroll.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. """
  2. Key bindings, for scrolling up and down through pages.
  3. This are separate bindings, because GNU readline doesn't have them, but
  4. they are very useful for navigating through long multiline buffers, like in
  5. Vi, Emacs, etc...
  6. """
  7. from __future__ import annotations
  8. from prompt_toolkit.key_binding.key_processor import KeyPressEvent
  9. __all__ = [
  10. "scroll_forward",
  11. "scroll_backward",
  12. "scroll_half_page_up",
  13. "scroll_half_page_down",
  14. "scroll_one_line_up",
  15. "scroll_one_line_down",
  16. ]
  17. E = KeyPressEvent
  18. def scroll_forward(event: E, half: bool = False) -> None:
  19. """
  20. Scroll window down.
  21. """
  22. w = event.app.layout.current_window
  23. b = event.app.current_buffer
  24. if w and w.render_info:
  25. info = w.render_info
  26. ui_content = info.ui_content
  27. # Height to scroll.
  28. scroll_height = info.window_height
  29. if half:
  30. scroll_height //= 2
  31. # Calculate how many lines is equivalent to that vertical space.
  32. y = b.document.cursor_position_row + 1
  33. height = 0
  34. while y < ui_content.line_count:
  35. line_height = info.get_height_for_line(y)
  36. if height + line_height < scroll_height:
  37. height += line_height
  38. y += 1
  39. else:
  40. break
  41. b.cursor_position = b.document.translate_row_col_to_index(y, 0)
  42. def scroll_backward(event: E, half: bool = False) -> None:
  43. """
  44. Scroll window up.
  45. """
  46. w = event.app.layout.current_window
  47. b = event.app.current_buffer
  48. if w and w.render_info:
  49. info = w.render_info
  50. # Height to scroll.
  51. scroll_height = info.window_height
  52. if half:
  53. scroll_height //= 2
  54. # Calculate how many lines is equivalent to that vertical space.
  55. y = max(0, b.document.cursor_position_row - 1)
  56. height = 0
  57. while y > 0:
  58. line_height = info.get_height_for_line(y)
  59. if height + line_height < scroll_height:
  60. height += line_height
  61. y -= 1
  62. else:
  63. break
  64. b.cursor_position = b.document.translate_row_col_to_index(y, 0)
  65. def scroll_half_page_down(event: E) -> None:
  66. """
  67. Same as ControlF, but only scroll half a page.
  68. """
  69. scroll_forward(event, half=True)
  70. def scroll_half_page_up(event: E) -> None:
  71. """
  72. Same as ControlB, but only scroll half a page.
  73. """
  74. scroll_backward(event, half=True)
  75. def scroll_one_line_down(event: E) -> None:
  76. """
  77. scroll_offset += 1
  78. """
  79. w = event.app.layout.current_window
  80. b = event.app.current_buffer
  81. if w:
  82. # When the cursor is at the top, move to the next line. (Otherwise, only scroll.)
  83. if w.render_info:
  84. info = w.render_info
  85. if w.vertical_scroll < info.content_height - info.window_height:
  86. if info.cursor_position.y <= info.configured_scroll_offsets.top:
  87. b.cursor_position += b.document.get_cursor_down_position()
  88. w.vertical_scroll += 1
  89. def scroll_one_line_up(event: E) -> None:
  90. """
  91. scroll_offset -= 1
  92. """
  93. w = event.app.layout.current_window
  94. b = event.app.current_buffer
  95. if w:
  96. # When the cursor is at the bottom, move to the previous line. (Otherwise, only scroll.)
  97. if w.render_info:
  98. info = w.render_info
  99. if w.vertical_scroll > 0:
  100. first_line_height = info.get_height_for_line(info.first_visible_line())
  101. cursor_up = info.cursor_position.y - (
  102. info.window_height
  103. - 1
  104. - first_line_height
  105. - info.configured_scroll_offsets.bottom
  106. )
  107. # Move cursor up, as many steps as the height of the first line.
  108. # TODO: not entirely correct yet, in case of line wrapping and many long lines.
  109. for _ in range(max(0, cursor_up)):
  110. b.cursor_position += b.document.get_cursor_up_position()
  111. # Scroll window
  112. w.vertical_scroll -= 1
  113. def scroll_page_down(event: E) -> None:
  114. """
  115. Scroll page down. (Prefer the cursor at the top of the page, after scrolling.)
  116. """
  117. w = event.app.layout.current_window
  118. b = event.app.current_buffer
  119. if w and w.render_info:
  120. # Scroll down one page.
  121. line_index = max(w.render_info.last_visible_line(), w.vertical_scroll + 1)
  122. w.vertical_scroll = line_index
  123. b.cursor_position = b.document.translate_row_col_to_index(line_index, 0)
  124. b.cursor_position += b.document.get_start_of_line_position(
  125. after_whitespace=True
  126. )
  127. def scroll_page_up(event: E) -> None:
  128. """
  129. Scroll page up. (Prefer the cursor at the bottom of the page, after scrolling.)
  130. """
  131. w = event.app.layout.current_window
  132. b = event.app.current_buffer
  133. if w and w.render_info:
  134. # Put cursor at the first visible line. (But make sure that the cursor
  135. # moves at least one line up.)
  136. line_index = max(
  137. 0,
  138. min(w.render_info.first_visible_line(), b.document.cursor_position_row - 1),
  139. )
  140. b.cursor_position = b.document.translate_row_col_to_index(line_index, 0)
  141. b.cursor_position += b.document.get_start_of_line_position(
  142. after_whitespace=True
  143. )
  144. # Set the scroll offset. We can safely set it to zero; the Window will
  145. # make sure that it scrolls at least until the cursor becomes visible.
  146. w.vertical_scroll = 0