llm-shell 0.9.2 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +61 -66
  3. data/lib/llm/shell/command.rb +40 -40
  4. data/lib/llm/shell/commands/clear_screen.rb +4 -18
  5. data/lib/llm/shell/commands/debug_mode.rb +12 -0
  6. data/lib/llm/shell/commands/dir_import.rb +4 -20
  7. data/lib/llm/shell/commands/disable_tool.rb +33 -0
  8. data/lib/llm/shell/commands/enable_tool.rb +33 -0
  9. data/lib/llm/shell/commands/file_import.rb +4 -20
  10. data/lib/llm/shell/commands/help.rb +23 -36
  11. data/lib/llm/shell/commands/show_chat.rb +4 -19
  12. data/lib/llm/shell/commands/show_version.rb +4 -20
  13. data/lib/llm/shell/commands/system_prompt.rb +4 -18
  14. data/lib/llm/shell/completion.rb +5 -5
  15. data/lib/llm/shell/config.rb +4 -5
  16. data/lib/llm/shell/formatter.rb +1 -2
  17. data/lib/llm/shell/internal/coderay/lib/coderay/duo.rb +81 -0
  18. data/lib/llm/shell/internal/coderay/lib/coderay/encoders/_map.rb +17 -0
  19. data/lib/llm/shell/internal/coderay/lib/coderay/encoders/comment_filter.rb +25 -0
  20. data/lib/llm/shell/internal/coderay/lib/coderay/encoders/count.rb +39 -0
  21. data/lib/llm/shell/internal/coderay/lib/coderay/encoders/debug.rb +49 -0
  22. data/lib/llm/shell/internal/coderay/lib/coderay/encoders/debug_lint.rb +63 -0
  23. data/lib/llm/shell/internal/coderay/lib/coderay/encoders/div.rb +23 -0
  24. data/lib/llm/shell/internal/coderay/lib/coderay/encoders/encoder.rb +190 -0
  25. data/lib/llm/shell/internal/coderay/lib/coderay/encoders/filter.rb +58 -0
  26. data/lib/llm/shell/internal/coderay/lib/coderay/encoders/html/css.rb +65 -0
  27. data/lib/llm/shell/internal/coderay/lib/coderay/encoders/html/numbering.rb +108 -0
  28. data/lib/llm/shell/internal/coderay/lib/coderay/encoders/html/output.rb +164 -0
  29. data/lib/llm/shell/internal/coderay/lib/coderay/encoders/html.rb +333 -0
  30. data/lib/llm/shell/internal/coderay/lib/coderay/encoders/json.rb +83 -0
  31. data/lib/llm/shell/internal/coderay/lib/coderay/encoders/lines_of_code.rb +45 -0
  32. data/lib/llm/shell/internal/coderay/lib/coderay/encoders/lint.rb +59 -0
  33. data/lib/llm/shell/internal/coderay/lib/coderay/encoders/null.rb +18 -0
  34. data/lib/llm/shell/internal/coderay/lib/coderay/encoders/page.rb +24 -0
  35. data/lib/llm/shell/internal/coderay/lib/coderay/encoders/span.rb +23 -0
  36. data/lib/llm/shell/internal/coderay/lib/coderay/encoders/statistic.rb +95 -0
  37. data/lib/llm/shell/internal/coderay/lib/coderay/encoders/terminal.rb +195 -0
  38. data/lib/llm/shell/internal/coderay/lib/coderay/encoders/text.rb +46 -0
  39. data/lib/llm/shell/internal/coderay/lib/coderay/encoders/token_kind_filter.rb +111 -0
  40. data/lib/llm/shell/internal/coderay/lib/coderay/encoders/xml.rb +72 -0
  41. data/lib/llm/shell/internal/coderay/lib/coderay/encoders/yaml.rb +50 -0
  42. data/lib/llm/shell/internal/coderay/lib/coderay/encoders.rb +18 -0
  43. data/lib/llm/shell/internal/coderay/lib/coderay/for_redcloth.rb +95 -0
  44. data/lib/llm/shell/internal/coderay/lib/coderay/helpers/file_type.rb +151 -0
  45. data/lib/llm/shell/internal/coderay/lib/coderay/helpers/plugin.rb +55 -0
  46. data/lib/llm/shell/internal/coderay/lib/coderay/helpers/plugin_host.rb +221 -0
  47. data/lib/llm/shell/internal/coderay/lib/coderay/helpers/word_list.rb +72 -0
  48. data/lib/llm/shell/internal/coderay/lib/coderay/scanners/_map.rb +24 -0
  49. data/lib/llm/shell/internal/coderay/lib/coderay/scanners/c.rb +189 -0
  50. data/lib/llm/shell/internal/coderay/lib/coderay/scanners/clojure.rb +217 -0
  51. data/lib/llm/shell/internal/coderay/lib/coderay/scanners/cpp.rb +217 -0
  52. data/lib/llm/shell/internal/coderay/lib/coderay/scanners/css.rb +196 -0
  53. data/lib/llm/shell/internal/coderay/lib/coderay/scanners/debug.rb +75 -0
  54. data/lib/llm/shell/internal/coderay/lib/coderay/scanners/delphi.rb +144 -0
  55. data/lib/llm/shell/internal/coderay/lib/coderay/scanners/diff.rb +221 -0
  56. data/lib/llm/shell/internal/coderay/lib/coderay/scanners/erb.rb +81 -0
  57. data/lib/llm/shell/internal/coderay/lib/coderay/scanners/go.rb +208 -0
  58. data/lib/llm/shell/internal/coderay/lib/coderay/scanners/groovy.rb +268 -0
  59. data/lib/llm/shell/internal/coderay/lib/coderay/scanners/haml.rb +168 -0
  60. data/lib/llm/shell/internal/coderay/lib/coderay/scanners/html.rb +275 -0
  61. data/lib/llm/shell/internal/coderay/lib/coderay/scanners/java/builtin_types.rb +421 -0
  62. data/lib/llm/shell/internal/coderay/lib/coderay/scanners/java.rb +174 -0
  63. data/lib/llm/shell/internal/coderay/lib/coderay/scanners/java_script.rb +236 -0
  64. data/lib/llm/shell/internal/coderay/lib/coderay/scanners/json.rb +98 -0
  65. data/lib/llm/shell/internal/coderay/lib/coderay/scanners/lua.rb +280 -0
  66. data/lib/llm/shell/internal/coderay/lib/coderay/scanners/php.rb +527 -0
  67. data/lib/llm/shell/internal/coderay/lib/coderay/scanners/python.rb +287 -0
  68. data/lib/llm/shell/internal/coderay/lib/coderay/scanners/raydebug.rb +75 -0
  69. data/lib/llm/shell/internal/coderay/lib/coderay/scanners/ruby/patterns.rb +178 -0
  70. data/lib/llm/shell/internal/coderay/lib/coderay/scanners/ruby/string_state.rb +79 -0
  71. data/lib/llm/shell/internal/coderay/lib/coderay/scanners/ruby.rb +477 -0
  72. data/lib/llm/shell/internal/coderay/lib/coderay/scanners/sass.rb +232 -0
  73. data/lib/llm/shell/internal/coderay/lib/coderay/scanners/scanner.rb +337 -0
  74. data/lib/llm/shell/internal/coderay/lib/coderay/scanners/sql.rb +169 -0
  75. data/lib/llm/shell/internal/coderay/lib/coderay/scanners/taskpaper.rb +36 -0
  76. data/lib/llm/shell/internal/coderay/lib/coderay/scanners/text.rb +26 -0
  77. data/lib/llm/shell/internal/coderay/lib/coderay/scanners/xml.rb +17 -0
  78. data/lib/llm/shell/internal/coderay/lib/coderay/scanners/yaml.rb +140 -0
  79. data/lib/llm/shell/internal/coderay/lib/coderay/scanners.rb +27 -0
  80. data/lib/llm/shell/internal/coderay/lib/coderay/styles/_map.rb +7 -0
  81. data/lib/llm/shell/internal/coderay/lib/coderay/styles/alpha.rb +153 -0
  82. data/lib/llm/shell/internal/coderay/lib/coderay/styles/style.rb +18 -0
  83. data/lib/llm/shell/internal/coderay/lib/coderay/styles.rb +15 -0
  84. data/lib/llm/shell/internal/coderay/lib/coderay/token_kinds.rb +85 -0
  85. data/lib/llm/shell/internal/coderay/lib/coderay/tokens.rb +164 -0
  86. data/lib/llm/shell/internal/coderay/lib/coderay/tokens_proxy.rb +55 -0
  87. data/lib/llm/shell/internal/coderay/lib/coderay/version.rb +3 -0
  88. data/lib/llm/shell/internal/coderay/lib/coderay.rb +284 -0
  89. data/lib/llm/shell/internal/io-line/lib/io/line/multiple.rb +19 -0
  90. data/lib/{io → llm/shell/internal/io-line/lib/io}/line.rb +2 -0
  91. data/lib/llm/shell/internal/llm.rb/lib/llm/bot/builder.rb +31 -0
  92. data/lib/llm/shell/internal/llm.rb/lib/llm/bot/conversable.rb +37 -0
  93. data/lib/llm/shell/internal/llm.rb/lib/llm/bot/prompt/completion.rb +49 -0
  94. data/lib/llm/shell/internal/llm.rb/lib/llm/bot/prompt/respond.rb +49 -0
  95. data/lib/llm/shell/internal/llm.rb/lib/llm/bot.rb +150 -0
  96. data/lib/llm/shell/internal/llm.rb/lib/llm/buffer.rb +162 -0
  97. data/lib/llm/shell/internal/llm.rb/lib/llm/client.rb +36 -0
  98. data/lib/llm/shell/internal/llm.rb/lib/llm/error.rb +49 -0
  99. data/lib/llm/shell/internal/llm.rb/lib/llm/eventhandler.rb +44 -0
  100. data/lib/llm/shell/internal/llm.rb/lib/llm/eventstream/event.rb +69 -0
  101. data/lib/llm/shell/internal/llm.rb/lib/llm/eventstream/parser.rb +88 -0
  102. data/lib/llm/shell/internal/llm.rb/lib/llm/eventstream.rb +8 -0
  103. data/lib/llm/shell/internal/llm.rb/lib/llm/file.rb +91 -0
  104. data/lib/llm/shell/internal/llm.rb/lib/llm/function.rb +177 -0
  105. data/lib/llm/shell/internal/llm.rb/lib/llm/message.rb +178 -0
  106. data/lib/llm/shell/internal/llm.rb/lib/llm/mime.rb +140 -0
  107. data/lib/llm/shell/internal/llm.rb/lib/llm/multipart.rb +101 -0
  108. data/lib/llm/shell/internal/llm.rb/lib/llm/object/builder.rb +38 -0
  109. data/lib/llm/shell/internal/llm.rb/lib/llm/object/kernel.rb +53 -0
  110. data/lib/llm/shell/internal/llm.rb/lib/llm/object.rb +89 -0
  111. data/lib/llm/shell/internal/llm.rb/lib/llm/provider.rb +352 -0
  112. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/anthropic/error_handler.rb +36 -0
  113. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/anthropic/files.rb +155 -0
  114. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/anthropic/format/completion_format.rb +88 -0
  115. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/anthropic/format.rb +29 -0
  116. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/anthropic/models.rb +54 -0
  117. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/anthropic/response/completion.rb +39 -0
  118. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/anthropic/response/enumerable.rb +11 -0
  119. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/anthropic/response/file.rb +23 -0
  120. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/anthropic/response/web_search.rb +21 -0
  121. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/anthropic/stream_parser.rb +66 -0
  122. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/anthropic.rb +138 -0
  123. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/deepseek/format/completion_format.rb +68 -0
  124. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/deepseek/format.rb +27 -0
  125. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/deepseek.rb +75 -0
  126. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/gemini/audio.rb +73 -0
  127. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/gemini/error_handler.rb +47 -0
  128. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/gemini/files.rb +146 -0
  129. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/gemini/format/completion_format.rb +69 -0
  130. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/gemini/format.rb +39 -0
  131. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/gemini/images.rb +133 -0
  132. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/gemini/models.rb +60 -0
  133. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/gemini/response/completion.rb +35 -0
  134. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/gemini/response/embedding.rb +8 -0
  135. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/gemini/response/file.rb +11 -0
  136. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/gemini/response/files.rb +15 -0
  137. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/gemini/response/image.rb +31 -0
  138. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/gemini/response/models.rb +15 -0
  139. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/gemini/response/web_search.rb +22 -0
  140. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/gemini/stream_parser.rb +86 -0
  141. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/gemini.rb +173 -0
  142. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/llamacpp.rb +74 -0
  143. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/ollama/error_handler.rb +36 -0
  144. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/ollama/format/completion_format.rb +77 -0
  145. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/ollama/format.rb +29 -0
  146. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/ollama/models.rb +56 -0
  147. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/ollama/response/completion.rb +28 -0
  148. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/ollama/response/embedding.rb +9 -0
  149. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/ollama/stream_parser.rb +44 -0
  150. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/ollama.rb +116 -0
  151. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/openai/audio.rb +91 -0
  152. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/openai/error_handler.rb +46 -0
  153. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/openai/files.rb +134 -0
  154. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/openai/format/completion_format.rb +90 -0
  155. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/openai/format/moderation_format.rb +35 -0
  156. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/openai/format/respond_format.rb +72 -0
  157. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/openai/format.rb +54 -0
  158. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/openai/images.rb +109 -0
  159. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/openai/models.rb +55 -0
  160. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/openai/moderations.rb +65 -0
  161. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/openai/response/audio.rb +7 -0
  162. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/openai/response/completion.rb +40 -0
  163. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/openai/response/embedding.rb +9 -0
  164. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/openai/response/enumerable.rb +23 -0
  165. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/openai/response/file.rb +7 -0
  166. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/openai/response/image.rb +16 -0
  167. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/openai/response/moderations.rb +34 -0
  168. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/openai/response/responds.rb +48 -0
  169. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/openai/response/web_search.rb +21 -0
  170. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/openai/responses/stream_parser.rb +76 -0
  171. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/openai/responses.rb +99 -0
  172. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/openai/stream_parser.rb +86 -0
  173. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/openai/vector_stores.rb +228 -0
  174. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/openai.rb +206 -0
  175. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/xai/images.rb +58 -0
  176. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/xai.rb +72 -0
  177. data/lib/llm/shell/internal/llm.rb/lib/llm/providers/zai.rb +74 -0
  178. data/lib/llm/shell/internal/llm.rb/lib/llm/response.rb +67 -0
  179. data/lib/llm/shell/internal/llm.rb/lib/llm/schema/array.rb +26 -0
  180. data/lib/llm/shell/internal/llm.rb/lib/llm/schema/boolean.rb +13 -0
  181. data/lib/llm/shell/internal/llm.rb/lib/llm/schema/integer.rb +43 -0
  182. data/lib/llm/shell/internal/llm.rb/lib/llm/schema/leaf.rb +78 -0
  183. data/lib/llm/shell/internal/llm.rb/lib/llm/schema/null.rb +13 -0
  184. data/lib/llm/shell/internal/llm.rb/lib/llm/schema/number.rb +43 -0
  185. data/lib/llm/shell/internal/llm.rb/lib/llm/schema/object.rb +41 -0
  186. data/lib/llm/shell/internal/llm.rb/lib/llm/schema/string.rb +34 -0
  187. data/lib/llm/shell/internal/llm.rb/lib/llm/schema/version.rb +8 -0
  188. data/lib/llm/shell/internal/llm.rb/lib/llm/schema.rb +81 -0
  189. data/lib/llm/shell/internal/llm.rb/lib/llm/server_tool.rb +32 -0
  190. data/lib/llm/shell/internal/llm.rb/lib/llm/tool/param.rb +75 -0
  191. data/lib/llm/shell/internal/llm.rb/lib/llm/tool.rb +78 -0
  192. data/lib/llm/shell/internal/llm.rb/lib/llm/utils.rb +19 -0
  193. data/lib/llm/shell/internal/llm.rb/lib/llm/version.rb +5 -0
  194. data/lib/llm/shell/internal/llm.rb/lib/llm.rb +121 -0
  195. data/lib/llm/shell/internal/optparse/lib/optionparser.rb +2 -0
  196. data/lib/llm/shell/internal/optparse/lib/optparse/ac.rb +70 -0
  197. data/lib/llm/shell/internal/optparse/lib/optparse/date.rb +18 -0
  198. data/lib/llm/shell/internal/optparse/lib/optparse/kwargs.rb +27 -0
  199. data/lib/llm/shell/internal/optparse/lib/optparse/shellwords.rb +7 -0
  200. data/lib/llm/shell/internal/optparse/lib/optparse/time.rb +11 -0
  201. data/lib/llm/shell/internal/optparse/lib/optparse/uri.rb +7 -0
  202. data/lib/llm/shell/internal/optparse/lib/optparse/version.rb +80 -0
  203. data/lib/llm/shell/internal/optparse/lib/optparse.rb +2469 -0
  204. data/lib/llm/shell/internal/paint/lib/paint/constants.rb +104 -0
  205. data/lib/llm/shell/internal/paint/lib/paint/pa.rb +13 -0
  206. data/lib/llm/shell/internal/paint/lib/paint/rgb_colors.rb +14 -0
  207. data/lib/llm/shell/internal/paint/lib/paint/shortcuts.rb +100 -0
  208. data/lib/llm/shell/internal/paint/lib/paint/shortcuts_version.rb +5 -0
  209. data/lib/llm/shell/internal/paint/lib/paint/util.rb +16 -0
  210. data/lib/llm/shell/internal/paint/lib/paint/version.rb +5 -0
  211. data/lib/llm/shell/internal/paint/lib/paint.rb +261 -0
  212. data/lib/llm/shell/internal/reline/lib/reline/config.rb +378 -0
  213. data/lib/llm/shell/internal/reline/lib/reline/face.rb +199 -0
  214. data/lib/llm/shell/internal/reline/lib/reline/history.rb +76 -0
  215. data/lib/llm/shell/internal/reline/lib/reline/io/ansi.rb +322 -0
  216. data/lib/llm/shell/internal/reline/lib/reline/io/dumb.rb +120 -0
  217. data/lib/llm/shell/internal/reline/lib/reline/io/windows.rb +530 -0
  218. data/lib/llm/shell/internal/reline/lib/reline/io.rb +55 -0
  219. data/lib/llm/shell/internal/reline/lib/reline/key_actor/base.rb +37 -0
  220. data/lib/llm/shell/internal/reline/lib/reline/key_actor/composite.rb +17 -0
  221. data/lib/llm/shell/internal/reline/lib/reline/key_actor/emacs.rb +517 -0
  222. data/lib/llm/shell/internal/reline/lib/reline/key_actor/vi_command.rb +518 -0
  223. data/lib/llm/shell/internal/reline/lib/reline/key_actor/vi_insert.rb +517 -0
  224. data/lib/llm/shell/internal/reline/lib/reline/key_actor.rb +8 -0
  225. data/lib/llm/shell/internal/reline/lib/reline/key_stroke.rb +119 -0
  226. data/lib/llm/shell/internal/reline/lib/reline/kill_ring.rb +125 -0
  227. data/lib/llm/shell/internal/reline/lib/reline/line_editor.rb +2356 -0
  228. data/lib/llm/shell/internal/reline/lib/reline/unicode/east_asian_width.rb +1292 -0
  229. data/lib/llm/shell/internal/reline/lib/reline/unicode.rb +421 -0
  230. data/lib/llm/shell/internal/reline/lib/reline/version.rb +3 -0
  231. data/lib/llm/shell/internal/reline/lib/reline.rb +527 -0
  232. data/lib/llm/shell/internal/tomlrb/lib/tomlrb/generated_parser.rb +712 -0
  233. data/lib/llm/shell/internal/tomlrb/lib/tomlrb/handler.rb +268 -0
  234. data/lib/llm/shell/internal/tomlrb/lib/tomlrb/local_date.rb +35 -0
  235. data/lib/llm/shell/internal/tomlrb/lib/tomlrb/local_date_time.rb +42 -0
  236. data/lib/llm/shell/internal/tomlrb/lib/tomlrb/local_time.rb +40 -0
  237. data/lib/llm/shell/internal/tomlrb/lib/tomlrb/parser.rb +21 -0
  238. data/lib/llm/shell/internal/tomlrb/lib/tomlrb/scanner.rb +92 -0
  239. data/lib/llm/shell/internal/tomlrb/lib/tomlrb/string_utils.rb +40 -0
  240. data/lib/llm/shell/internal/tomlrb/lib/tomlrb/version.rb +5 -0
  241. data/lib/llm/shell/internal/tomlrb/lib/tomlrb.rb +49 -0
  242. data/lib/llm/shell/options.rb +1 -1
  243. data/lib/llm/shell/renderer.rb +2 -3
  244. data/lib/llm/shell/repl.rb +21 -16
  245. data/lib/llm/shell/tool.rb +42 -0
  246. data/lib/llm/shell/tools/read_file.rb +15 -0
  247. data/lib/llm/shell/tools/system.rb +17 -0
  248. data/lib/llm/shell/tools/write_file.rb +16 -0
  249. data/lib/llm/shell/version.rb +1 -1
  250. data/lib/llm/shell.rb +83 -39
  251. data/libexec/llm-shell/shell +4 -6
  252. data/llm-shell.gemspec +0 -4
  253. metadata +233 -63
  254. data/lib/llm/function.rb +0 -17
  255. data/lib/llm/shell/command/extension.rb +0 -42
  256. data/lib/llm/shell/commands/utils.rb +0 -21
  257. data/lib/llm/shell/functions/read_file.rb +0 -22
  258. data/lib/llm/shell/functions/write_file.rb +0 -22
@@ -0,0 +1,2356 @@
1
+ require 'reline/kill_ring'
2
+ require 'reline/unicode'
3
+
4
+ require 'tempfile'
5
+
6
+ class Reline::LineEditor
7
+ # TODO: Use "private alias_method" idiom after drop Ruby 2.5.
8
+ attr_reader :byte_pointer
9
+ attr_accessor :confirm_multiline_termination_proc
10
+ attr_accessor :completion_proc
11
+ attr_accessor :completion_append_character
12
+ attr_accessor :output_modifier_proc
13
+ attr_accessor :prompt_proc
14
+ attr_accessor :auto_indent_proc
15
+ attr_accessor :dig_perfect_match_proc
16
+
17
+ VI_MOTIONS = %i{
18
+ ed_prev_char
19
+ ed_next_char
20
+ vi_zero
21
+ ed_move_to_beg
22
+ ed_move_to_end
23
+ vi_to_column
24
+ vi_next_char
25
+ vi_prev_char
26
+ vi_next_word
27
+ vi_prev_word
28
+ vi_to_next_char
29
+ vi_to_prev_char
30
+ vi_end_word
31
+ vi_next_big_word
32
+ vi_prev_big_word
33
+ vi_end_big_word
34
+ }
35
+
36
+ module CompletionState
37
+ NORMAL = :normal
38
+ MENU = :menu
39
+ MENU_WITH_PERFECT_MATCH = :menu_with_perfect_match
40
+ PERFECT_MATCH = :perfect_match
41
+ end
42
+
43
+ RenderedScreen = Struct.new(:base_y, :lines, :cursor_y, keyword_init: true)
44
+
45
+ CompletionJourneyState = Struct.new(:line_index, :pre, :target, :post, :list, :pointer)
46
+ NullActionState = [nil, nil].freeze
47
+
48
+ class MenuInfo
49
+ attr_reader :list
50
+
51
+ def initialize(list)
52
+ @list = list
53
+ end
54
+
55
+ def lines(screen_width)
56
+ return [] if @list.empty?
57
+
58
+ list = @list.sort
59
+ sizes = list.map { |item| Reline::Unicode.calculate_width(item) }
60
+ item_width = sizes.max + 2
61
+ num_cols = [screen_width / item_width, 1].max
62
+ num_rows = list.size.fdiv(num_cols).ceil
63
+ list_with_padding = list.zip(sizes).map { |item, size| item + ' ' * (item_width - size) }
64
+ aligned = (list_with_padding + [nil] * (num_rows * num_cols - list_with_padding.size)).each_slice(num_rows).to_a.transpose
65
+ aligned.map do |row|
66
+ row.join.rstrip
67
+ end
68
+ end
69
+ end
70
+
71
+ MINIMUM_SCROLLBAR_HEIGHT = 1
72
+
73
+ def initialize(config)
74
+ @config = config
75
+ @completion_append_character = ''
76
+ @screen_size = [0, 0] # Should be initialized with actual winsize in LineEditor#reset
77
+ reset_variables
78
+ end
79
+
80
+ def io_gate
81
+ Reline::IOGate
82
+ end
83
+
84
+ def encoding
85
+ io_gate.encoding
86
+ end
87
+
88
+ def set_pasting_state(in_pasting)
89
+ # While pasting, text to be inserted is stored to @continuous_insertion_buffer.
90
+ # After pasting, this buffer should be force inserted.
91
+ process_insert(force: true) if @in_pasting && !in_pasting
92
+ @in_pasting = in_pasting
93
+ end
94
+
95
+ private def check_mode_string
96
+ if @config.show_mode_in_prompt
97
+ if @config.editing_mode_is?(:vi_command)
98
+ @config.vi_cmd_mode_string
99
+ elsif @config.editing_mode_is?(:vi_insert)
100
+ @config.vi_ins_mode_string
101
+ elsif @config.editing_mode_is?(:emacs)
102
+ @config.emacs_mode_string
103
+ else
104
+ '?'
105
+ end
106
+ end
107
+ end
108
+
109
+ private def check_multiline_prompt(buffer, mode_string)
110
+ if @vi_arg
111
+ prompt = "(arg: #{@vi_arg}) "
112
+ elsif @searching_prompt
113
+ prompt = @searching_prompt
114
+ else
115
+ prompt = @prompt
116
+ end
117
+ if !@is_multiline
118
+ mode_string = check_mode_string
119
+ prompt = mode_string + prompt if mode_string
120
+ [prompt] + [''] * (buffer.size - 1)
121
+ elsif @prompt_proc
122
+ prompt_list = @prompt_proc.(buffer).map { |pr| pr.gsub("\n", "\\n") }
123
+ prompt_list.map!{ prompt } if @vi_arg or @searching_prompt
124
+ prompt_list = [prompt] if prompt_list.empty?
125
+ prompt_list = prompt_list.map{ |pr| mode_string + pr } if mode_string
126
+ prompt = prompt_list[@line_index]
127
+ prompt = prompt_list[0] if prompt.nil?
128
+ prompt = prompt_list.last if prompt.nil?
129
+ if buffer.size > prompt_list.size
130
+ (buffer.size - prompt_list.size).times do
131
+ prompt_list << prompt_list.last
132
+ end
133
+ end
134
+ prompt_list
135
+ else
136
+ prompt = mode_string + prompt if mode_string
137
+ [prompt] * buffer.size
138
+ end
139
+ end
140
+
141
+ def reset(prompt = '')
142
+ @screen_size = Reline::IOGate.get_screen_size
143
+ reset_variables(prompt)
144
+ @rendered_screen.base_y = Reline::IOGate.cursor_pos.y
145
+ if ENV.key?('RELINE_ALT_SCROLLBAR')
146
+ @full_block = '::'
147
+ @upper_half_block = "''"
148
+ @lower_half_block = '..'
149
+ @block_elem_width = 2
150
+ elsif Reline::IOGate.win?
151
+ @full_block = '█'
152
+ @upper_half_block = '▀'
153
+ @lower_half_block = '▄'
154
+ @block_elem_width = 1
155
+ elsif encoding == Encoding::UTF_8
156
+ @full_block = '█'
157
+ @upper_half_block = '▀'
158
+ @lower_half_block = '▄'
159
+ @block_elem_width = Reline::Unicode.calculate_width('█')
160
+ else
161
+ @full_block = '::'
162
+ @upper_half_block = "''"
163
+ @lower_half_block = '..'
164
+ @block_elem_width = 2
165
+ end
166
+ end
167
+
168
+ def handle_signal
169
+ handle_interrupted
170
+ handle_resized
171
+ end
172
+
173
+ private def handle_resized
174
+ return unless @resized
175
+
176
+ @screen_size = Reline::IOGate.get_screen_size
177
+ @resized = false
178
+ scroll_into_view
179
+ Reline::IOGate.move_cursor_up @rendered_screen.cursor_y
180
+ @rendered_screen.base_y = Reline::IOGate.cursor_pos.y
181
+ clear_rendered_screen_cache
182
+ render
183
+ end
184
+
185
+ private def handle_interrupted
186
+ return unless @interrupted
187
+
188
+ @interrupted = false
189
+ clear_dialogs
190
+ render
191
+ cursor_to_bottom_offset = @rendered_screen.lines.size - @rendered_screen.cursor_y
192
+ Reline::IOGate.scroll_down cursor_to_bottom_offset
193
+ Reline::IOGate.move_cursor_column 0
194
+ clear_rendered_screen_cache
195
+ case @old_trap
196
+ when 'DEFAULT', 'SYSTEM_DEFAULT'
197
+ raise Interrupt
198
+ when 'IGNORE'
199
+ # Do nothing
200
+ when 'EXIT'
201
+ exit
202
+ else
203
+ @old_trap.call if @old_trap.respond_to?(:call)
204
+ end
205
+ end
206
+
207
+ def set_signal_handlers
208
+ Reline::IOGate.set_winch_handler do
209
+ @resized = true
210
+ end
211
+ @old_trap = Signal.trap('INT') do
212
+ @interrupted = true
213
+ end
214
+ end
215
+
216
+ def finalize
217
+ Signal.trap('INT', @old_trap)
218
+ end
219
+
220
+ def eof?
221
+ @eof
222
+ end
223
+
224
+ def reset_variables(prompt = '')
225
+ @prompt = prompt.gsub("\n", "\\n")
226
+ @mark_pointer = nil
227
+ @is_multiline = false
228
+ @finished = false
229
+ @history_pointer = nil
230
+ @kill_ring ||= Reline::KillRing.new
231
+ @vi_clipboard = ''
232
+ @vi_arg = nil
233
+ @waiting_proc = nil
234
+ @vi_waiting_operator = nil
235
+ @vi_waiting_operator_arg = nil
236
+ @completion_journey_state = nil
237
+ @completion_state = CompletionState::NORMAL
238
+ @perfect_matched = nil
239
+ @menu_info = nil
240
+ @searching_prompt = nil
241
+ @just_cursor_moving = false
242
+ @eof = false
243
+ @continuous_insertion_buffer = String.new(encoding: encoding)
244
+ @scroll_partial_screen = 0
245
+ @drop_terminate_spaces = false
246
+ @in_pasting = false
247
+ @auto_indent_proc = nil
248
+ @dialogs = []
249
+ @interrupted = false
250
+ @resized = false
251
+ @cache = {}
252
+ @rendered_screen = RenderedScreen.new(base_y: 0, lines: [], cursor_y: 0)
253
+ @undo_redo_history = [[[""], 0, 0]]
254
+ @undo_redo_index = 0
255
+ @restoring = false
256
+ @prev_action_state = NullActionState
257
+ @next_action_state = NullActionState
258
+ reset_line
259
+ end
260
+
261
+ def reset_line
262
+ @byte_pointer = 0
263
+ @buffer_of_lines = [String.new(encoding: encoding)]
264
+ @line_index = 0
265
+ @cache.clear
266
+ @line_backup_in_history = nil
267
+ end
268
+
269
+ def multiline_on
270
+ @is_multiline = true
271
+ end
272
+
273
+ def multiline_off
274
+ @is_multiline = false
275
+ end
276
+
277
+ private def insert_new_line(cursor_line, next_line)
278
+ @buffer_of_lines.insert(@line_index + 1, String.new(next_line, encoding: encoding))
279
+ @buffer_of_lines[@line_index] = cursor_line
280
+ @line_index += 1
281
+ @byte_pointer = 0
282
+ if @auto_indent_proc && !@in_pasting
283
+ if next_line.empty?
284
+ (
285
+ # For compatibility, use this calculation instead of just `process_auto_indent @line_index - 1, cursor_dependent: false`
286
+ indent1 = @auto_indent_proc.(@buffer_of_lines.take(@line_index - 1).push(''), @line_index - 1, 0, true)
287
+ indent2 = @auto_indent_proc.(@buffer_of_lines.take(@line_index), @line_index - 1, @buffer_of_lines[@line_index - 1].bytesize, false)
288
+ indent = indent2 || indent1
289
+ @buffer_of_lines[@line_index - 1] = ' ' * indent + @buffer_of_lines[@line_index - 1].gsub(/\A\s*/, '')
290
+ )
291
+ process_auto_indent @line_index, add_newline: true
292
+ else
293
+ process_auto_indent @line_index - 1, cursor_dependent: false
294
+ process_auto_indent @line_index, add_newline: true # Need for compatibility
295
+ process_auto_indent @line_index, cursor_dependent: false
296
+ end
297
+ end
298
+ end
299
+
300
+ private def split_line_by_width(str, max_width, offset: 0)
301
+ Reline::Unicode.split_line_by_width(str, max_width, encoding, offset: offset)
302
+ end
303
+
304
+ def current_byte_pointer_cursor
305
+ calculate_width(current_line.byteslice(0, @byte_pointer))
306
+ end
307
+
308
+ private def calculate_nearest_cursor(cursor)
309
+ line_to_calc = current_line
310
+ new_cursor_max = calculate_width(line_to_calc)
311
+ new_cursor = 0
312
+ new_byte_pointer = 0
313
+ height = 1
314
+ max_width = screen_width
315
+ if @config.editing_mode_is?(:vi_command)
316
+ last_byte_size = Reline::Unicode.get_prev_mbchar_size(line_to_calc, line_to_calc.bytesize)
317
+ if last_byte_size > 0
318
+ last_mbchar = line_to_calc.byteslice(line_to_calc.bytesize - last_byte_size, last_byte_size)
319
+ last_width = Reline::Unicode.get_mbchar_width(last_mbchar)
320
+ end_of_line_cursor = new_cursor_max - last_width
321
+ else
322
+ end_of_line_cursor = new_cursor_max
323
+ end
324
+ else
325
+ end_of_line_cursor = new_cursor_max
326
+ end
327
+ line_to_calc.grapheme_clusters.each do |gc|
328
+ mbchar = gc.encode(Encoding::UTF_8)
329
+ mbchar_width = Reline::Unicode.get_mbchar_width(mbchar)
330
+ now = new_cursor + mbchar_width
331
+ if now > end_of_line_cursor or now > cursor
332
+ break
333
+ end
334
+ new_cursor += mbchar_width
335
+ if new_cursor > max_width * height
336
+ height += 1
337
+ end
338
+ new_byte_pointer += gc.bytesize
339
+ end
340
+ @byte_pointer = new_byte_pointer
341
+ end
342
+
343
+ def with_cache(key, *deps)
344
+ cached_deps, value = @cache[key]
345
+ if cached_deps != deps
346
+ @cache[key] = [deps, value = yield(*deps, cached_deps, value)]
347
+ end
348
+ value
349
+ end
350
+
351
+ def modified_lines
352
+ with_cache(__method__, whole_lines, finished?) do |whole, complete|
353
+ modify_lines(whole, complete)
354
+ end
355
+ end
356
+
357
+ def prompt_list
358
+ with_cache(__method__, whole_lines, check_mode_string, @vi_arg, @searching_prompt) do |lines, mode_string|
359
+ check_multiline_prompt(lines, mode_string)
360
+ end
361
+ end
362
+
363
+ def screen_height
364
+ @screen_size.first
365
+ end
366
+
367
+ def screen_width
368
+ @screen_size.last
369
+ end
370
+
371
+ def screen_scroll_top
372
+ @scroll_partial_screen
373
+ end
374
+
375
+ def wrapped_prompt_and_input_lines
376
+ with_cache(__method__, @buffer_of_lines.size, modified_lines, prompt_list, screen_width) do |n, lines, prompts, width, prev_cache_key, cached_value|
377
+ prev_n, prev_lines, prev_prompts, prev_width = prev_cache_key
378
+ cached_wraps = {}
379
+ if prev_width == width
380
+ prev_n.times do |i|
381
+ cached_wraps[[prev_prompts[i], prev_lines[i]]] = cached_value[i]
382
+ end
383
+ end
384
+
385
+ n.times.map do |i|
386
+ prompt = prompts[i] || ''
387
+ line = lines[i] || ''
388
+ if (cached = cached_wraps[[prompt, line]])
389
+ next cached
390
+ end
391
+ *wrapped_prompts, code_line_prompt = split_line_by_width(prompt, width)
392
+ wrapped_lines = split_line_by_width(line, width, offset: calculate_width(code_line_prompt, true))
393
+ wrapped_prompts.map { |p| [p, ''] } + [[code_line_prompt, wrapped_lines.first]] + wrapped_lines.drop(1).map { |c| ['', c] }
394
+ end
395
+ end
396
+ end
397
+
398
+ def calculate_overlay_levels(overlay_levels)
399
+ levels = []
400
+ overlay_levels.each do |x, w, l|
401
+ levels.fill(l, x, w)
402
+ end
403
+ levels
404
+ end
405
+
406
+ def render_line_differential(old_items, new_items)
407
+ old_levels = calculate_overlay_levels(old_items.zip(new_items).each_with_index.map {|((x, w, c), (nx, _nw, nc)), i| [x, w, c == nc && x == nx ? i : -1] if x }.compact)
408
+ new_levels = calculate_overlay_levels(new_items.each_with_index.map { |(x, w), i| [x, w, i] if x }.compact).take(screen_width)
409
+ base_x = 0
410
+ new_levels.zip(old_levels).chunk { |n, o| n == o ? :skip : n || :blank }.each do |level, chunk|
411
+ width = chunk.size
412
+ if level == :skip
413
+ # do nothing
414
+ elsif level == :blank
415
+ Reline::IOGate.move_cursor_column base_x
416
+ Reline::IOGate.write "#{Reline::IOGate.reset_color_sequence}#{' ' * width}"
417
+ else
418
+ x, w, content = new_items[level]
419
+ cover_begin = base_x != 0 && new_levels[base_x - 1] == level
420
+ cover_end = new_levels[base_x + width] == level
421
+ pos = 0
422
+ unless x == base_x && w == width
423
+ content, pos = Reline::Unicode.take_mbchar_range(content, base_x - x, width, cover_begin: cover_begin, cover_end: cover_end, padding: true)
424
+ end
425
+ Reline::IOGate.move_cursor_column x + pos
426
+ Reline::IOGate.write "#{Reline::IOGate.reset_color_sequence}#{content}#{Reline::IOGate.reset_color_sequence}"
427
+ end
428
+ base_x += width
429
+ end
430
+ if old_levels.size > new_levels.size
431
+ Reline::IOGate.move_cursor_column new_levels.size
432
+ Reline::IOGate.erase_after_cursor
433
+ end
434
+ end
435
+
436
+ # Calculate cursor position in word wrapped content.
437
+ def wrapped_cursor_position
438
+ prompt_width = calculate_width(prompt_list[@line_index], true)
439
+ line_before_cursor = Reline::Unicode.escape_for_print(whole_lines[@line_index].byteslice(0, @byte_pointer))
440
+ wrapped_line_before_cursor = split_line_by_width(' ' * prompt_width + line_before_cursor, screen_width)
441
+ wrapped_cursor_y = wrapped_prompt_and_input_lines[0...@line_index].sum(&:size) + wrapped_line_before_cursor.size - 1
442
+ wrapped_cursor_x = calculate_width(wrapped_line_before_cursor.last)
443
+ [wrapped_cursor_x, wrapped_cursor_y]
444
+ end
445
+
446
+ def clear_dialogs
447
+ @dialogs.each do |dialog|
448
+ dialog.contents = nil
449
+ dialog.trap_key = nil
450
+ end
451
+ end
452
+
453
+ def update_dialogs(key = nil)
454
+ wrapped_cursor_x, wrapped_cursor_y = wrapped_cursor_position
455
+ @dialogs.each do |dialog|
456
+ dialog.trap_key = nil
457
+ update_each_dialog(dialog, wrapped_cursor_x, wrapped_cursor_y - screen_scroll_top, key)
458
+ end
459
+ end
460
+
461
+ def render_finished
462
+ Reline::IOGate.buffered_output do
463
+ render_differential([], 0, 0)
464
+ lines = @buffer_of_lines.size.times.map do |i|
465
+ line = Reline::Unicode.strip_non_printing_start_end(prompt_list[i]) + modified_lines[i]
466
+ wrapped_lines = split_line_by_width(line, screen_width)
467
+ wrapped_lines.last.empty? ? "#{line} " : line
468
+ end
469
+ Reline::IOGate.write lines.map { |l| "#{l}\r\n" }.join
470
+ end
471
+ end
472
+
473
+ def render
474
+ wrapped_cursor_x, wrapped_cursor_y = wrapped_cursor_position
475
+ new_lines = wrapped_prompt_and_input_lines.flatten(1)[screen_scroll_top, screen_height].map do |prompt, line|
476
+ prompt_width = Reline::Unicode.calculate_width(prompt, true)
477
+ [[0, prompt_width, prompt], [prompt_width, Reline::Unicode.calculate_width(line, true), line]]
478
+ end
479
+ if @menu_info
480
+ @menu_info.lines(screen_width).each do |item|
481
+ new_lines << [[0, Reline::Unicode.calculate_width(item), item]]
482
+ end
483
+ @menu_info = nil # TODO: do not change state here
484
+ end
485
+
486
+ @dialogs.each_with_index do |dialog, index|
487
+ next unless dialog.contents
488
+
489
+ x_range, y_range = dialog_range dialog, wrapped_cursor_y - screen_scroll_top
490
+ y_range.each do |row|
491
+ next if row < 0 || row >= screen_height
492
+
493
+ dialog_rows = new_lines[row] ||= []
494
+ # index 0 is for prompt, index 1 is for line, index 2.. is for dialog
495
+ dialog_rows[index + 2] = [x_range.begin, dialog.width, dialog.contents[row - y_range.begin]]
496
+ end
497
+ end
498
+
499
+ Reline::IOGate.buffered_output do
500
+ render_differential new_lines, wrapped_cursor_x, wrapped_cursor_y - screen_scroll_top
501
+ end
502
+ end
503
+
504
+ # Reflects lines to be rendered and new cursor position to the screen
505
+ # by calculating the difference from the previous render.
506
+
507
+ private def render_differential(new_lines, new_cursor_x, new_cursor_y)
508
+ Reline::IOGate.disable_auto_linewrap(true) if Reline::IOGate.win?
509
+ rendered_lines = @rendered_screen.lines
510
+ cursor_y = @rendered_screen.cursor_y
511
+ if new_lines != rendered_lines
512
+ # Hide cursor while rendering to avoid cursor flickering.
513
+ Reline::IOGate.hide_cursor
514
+ num_lines = [[new_lines.size, rendered_lines.size].max, screen_height].min
515
+ if @rendered_screen.base_y + num_lines > screen_height
516
+ Reline::IOGate.scroll_down(num_lines - cursor_y - 1)
517
+ @rendered_screen.base_y = screen_height - num_lines
518
+ cursor_y = num_lines - 1
519
+ end
520
+ num_lines.times do |i|
521
+ rendered_line = rendered_lines[i] || []
522
+ line_to_render = new_lines[i] || []
523
+ next if rendered_line == line_to_render
524
+
525
+ Reline::IOGate.move_cursor_down i - cursor_y
526
+ cursor_y = i
527
+ unless rendered_lines[i]
528
+ Reline::IOGate.move_cursor_column 0
529
+ Reline::IOGate.erase_after_cursor
530
+ end
531
+ render_line_differential(rendered_line, line_to_render)
532
+ end
533
+ @rendered_screen.lines = new_lines
534
+ Reline::IOGate.show_cursor
535
+ end
536
+ Reline::IOGate.move_cursor_column new_cursor_x
537
+ new_cursor_y = new_cursor_y.clamp(0, screen_height - 1)
538
+ Reline::IOGate.move_cursor_down new_cursor_y - cursor_y
539
+ @rendered_screen.cursor_y = new_cursor_y
540
+ ensure
541
+ Reline::IOGate.disable_auto_linewrap(false) if Reline::IOGate.win?
542
+ end
543
+
544
+ private def clear_rendered_screen_cache
545
+ @rendered_screen.lines = []
546
+ @rendered_screen.cursor_y = 0
547
+ end
548
+
549
+ def upper_space_height(wrapped_cursor_y)
550
+ wrapped_cursor_y - screen_scroll_top
551
+ end
552
+
553
+ def rest_height(wrapped_cursor_y)
554
+ screen_height - wrapped_cursor_y + screen_scroll_top - @rendered_screen.base_y - 1
555
+ end
556
+
557
+ def rerender
558
+ render unless @in_pasting
559
+ end
560
+
561
+ class DialogProcScope
562
+ CompletionJourneyData = Struct.new(:preposing, :postposing, :list, :pointer)
563
+
564
+ def initialize(line_editor, config, proc_to_exec, context)
565
+ @line_editor = line_editor
566
+ @config = config
567
+ @proc_to_exec = proc_to_exec
568
+ @context = context
569
+ @cursor_pos = Reline::CursorPos.new
570
+ end
571
+
572
+ def context
573
+ @context
574
+ end
575
+
576
+ def retrieve_completion_block(_unused = false)
577
+ preposing, target, postposing, _quote = @line_editor.retrieve_completion_block
578
+ [preposing, target, postposing]
579
+ end
580
+
581
+ def call_completion_proc_with_checking_args(pre, target, post)
582
+ @line_editor.call_completion_proc_with_checking_args(pre, target, post)
583
+ end
584
+
585
+ def set_dialog(dialog)
586
+ @dialog = dialog
587
+ end
588
+
589
+ def dialog
590
+ @dialog
591
+ end
592
+
593
+ def set_cursor_pos(col, row)
594
+ @cursor_pos.x = col
595
+ @cursor_pos.y = row
596
+ end
597
+
598
+ def set_key(key)
599
+ @key = key
600
+ end
601
+
602
+ def key
603
+ @key
604
+ end
605
+
606
+ def cursor_pos
607
+ @cursor_pos
608
+ end
609
+
610
+ def just_cursor_moving
611
+ @line_editor.instance_variable_get(:@just_cursor_moving)
612
+ end
613
+
614
+ def screen_width
615
+ @line_editor.screen_width
616
+ end
617
+
618
+ def screen_height
619
+ @line_editor.screen_height
620
+ end
621
+
622
+ def preferred_dialog_height
623
+ _wrapped_cursor_x, wrapped_cursor_y = @line_editor.wrapped_cursor_position
624
+ [@line_editor.upper_space_height(wrapped_cursor_y), @line_editor.rest_height(wrapped_cursor_y), (screen_height + 6) / 5].max
625
+ end
626
+
627
+ def completion_journey_data
628
+ @line_editor.dialog_proc_scope_completion_journey_data
629
+ end
630
+
631
+ def config
632
+ @config
633
+ end
634
+
635
+ def call
636
+ instance_exec(&@proc_to_exec)
637
+ end
638
+ end
639
+
640
+ class Dialog
641
+ attr_reader :name, :contents, :width
642
+ attr_accessor :scroll_top, :pointer, :column, :vertical_offset, :trap_key
643
+
644
+ def initialize(name, config, proc_scope)
645
+ @name = name
646
+ @config = config
647
+ @proc_scope = proc_scope
648
+ @width = nil
649
+ @scroll_top = 0
650
+ @trap_key = nil
651
+ end
652
+
653
+ def set_cursor_pos(col, row)
654
+ @proc_scope.set_cursor_pos(col, row)
655
+ end
656
+
657
+ def width=(v)
658
+ @width = v
659
+ end
660
+
661
+ def contents=(contents)
662
+ @contents = contents
663
+ if contents and @width.nil?
664
+ @width = contents.map{ |line| Reline::Unicode.calculate_width(line, true) }.max
665
+ end
666
+ end
667
+
668
+ def call(key)
669
+ @proc_scope.set_dialog(self)
670
+ @proc_scope.set_key(key)
671
+ dialog_render_info = @proc_scope.call
672
+ if @trap_key
673
+ if @trap_key.any?{ |i| i.is_a?(Array) } # multiple trap
674
+ @trap_key.each do |t|
675
+ @config.add_oneshot_key_binding(t, @name)
676
+ end
677
+ else
678
+ @config.add_oneshot_key_binding(@trap_key, @name)
679
+ end
680
+ end
681
+ dialog_render_info
682
+ end
683
+ end
684
+
685
+ def add_dialog_proc(name, p, context = nil)
686
+ dialog = Dialog.new(name, @config, DialogProcScope.new(self, @config, p, context))
687
+ if index = @dialogs.find_index { |d| d.name == name }
688
+ @dialogs[index] = dialog
689
+ else
690
+ @dialogs << dialog
691
+ end
692
+ end
693
+
694
+ DIALOG_DEFAULT_HEIGHT = 20
695
+
696
+ private def dialog_range(dialog, dialog_y)
697
+ x_range = dialog.column...dialog.column + dialog.width
698
+ y_range = dialog_y + dialog.vertical_offset...dialog_y + dialog.vertical_offset + dialog.contents.size
699
+ [x_range, y_range]
700
+ end
701
+
702
+ private def update_each_dialog(dialog, cursor_column, cursor_row, key = nil)
703
+ dialog.set_cursor_pos(cursor_column, cursor_row)
704
+ dialog_render_info = dialog.call(key)
705
+ if dialog_render_info.nil? or dialog_render_info.contents.nil? or dialog_render_info.contents.empty?
706
+ dialog.contents = nil
707
+ dialog.trap_key = nil
708
+ return
709
+ end
710
+ contents = dialog_render_info.contents
711
+ pointer = dialog.pointer
712
+ if dialog_render_info.width
713
+ dialog.width = dialog_render_info.width
714
+ else
715
+ dialog.width = contents.map { |l| calculate_width(l, true) }.max
716
+ end
717
+ height = dialog_render_info.height || DIALOG_DEFAULT_HEIGHT
718
+ height = contents.size if contents.size < height
719
+ if contents.size > height
720
+ if dialog.pointer
721
+ if dialog.pointer < 0
722
+ dialog.scroll_top = 0
723
+ elsif (dialog.pointer - dialog.scroll_top) >= (height - 1)
724
+ dialog.scroll_top = dialog.pointer - (height - 1)
725
+ elsif (dialog.pointer - dialog.scroll_top) < 0
726
+ dialog.scroll_top = dialog.pointer
727
+ end
728
+ pointer = dialog.pointer - dialog.scroll_top
729
+ else
730
+ dialog.scroll_top = 0
731
+ end
732
+ contents = contents[dialog.scroll_top, height]
733
+ end
734
+ if dialog_render_info.scrollbar and dialog_render_info.contents.size > height
735
+ bar_max_height = height * 2
736
+ moving_distance = (dialog_render_info.contents.size - height) * 2
737
+ position_ratio = dialog.scroll_top.zero? ? 0.0 : ((dialog.scroll_top * 2).to_f / moving_distance)
738
+ bar_height = (bar_max_height * ((contents.size * 2).to_f / (dialog_render_info.contents.size * 2))).floor.to_i
739
+ bar_height = MINIMUM_SCROLLBAR_HEIGHT if bar_height < MINIMUM_SCROLLBAR_HEIGHT
740
+ scrollbar_pos = ((bar_max_height - bar_height) * position_ratio).floor.to_i
741
+ else
742
+ scrollbar_pos = nil
743
+ end
744
+ dialog.column = dialog_render_info.pos.x
745
+ dialog.width += @block_elem_width if scrollbar_pos
746
+ diff = (dialog.column + dialog.width) - screen_width
747
+ if diff > 0
748
+ dialog.column -= diff
749
+ end
750
+ if rest_height(screen_scroll_top + cursor_row) - dialog_render_info.pos.y >= height
751
+ dialog.vertical_offset = dialog_render_info.pos.y + 1
752
+ elsif cursor_row >= height
753
+ dialog.vertical_offset = dialog_render_info.pos.y - height
754
+ else
755
+ dialog.vertical_offset = dialog_render_info.pos.y + 1
756
+ end
757
+ if dialog.column < 0
758
+ dialog.column = 0
759
+ dialog.width = screen_width
760
+ end
761
+ face = Reline::Face[dialog_render_info.face || :default]
762
+ scrollbar_sgr = face[:scrollbar]
763
+ default_sgr = face[:default]
764
+ enhanced_sgr = face[:enhanced]
765
+ dialog.contents = contents.map.with_index do |item, i|
766
+ line_sgr = i == pointer ? enhanced_sgr : default_sgr
767
+ str_width = dialog.width - (scrollbar_pos.nil? ? 0 : @block_elem_width)
768
+ str, = Reline::Unicode.take_mbchar_range(item, 0, str_width, padding: true)
769
+ colored_content = "#{line_sgr}#{str}"
770
+ if scrollbar_pos
771
+ if scrollbar_pos <= (i * 2) and (i * 2 + 1) < (scrollbar_pos + bar_height)
772
+ colored_content + scrollbar_sgr + @full_block
773
+ elsif scrollbar_pos <= (i * 2) and (i * 2) < (scrollbar_pos + bar_height)
774
+ colored_content + scrollbar_sgr + @upper_half_block
775
+ elsif scrollbar_pos <= (i * 2 + 1) and (i * 2) < (scrollbar_pos + bar_height)
776
+ colored_content + scrollbar_sgr + @lower_half_block
777
+ else
778
+ colored_content + scrollbar_sgr + ' ' * @block_elem_width
779
+ end
780
+ else
781
+ colored_content
782
+ end
783
+ end
784
+ end
785
+
786
+ private def modify_lines(before, complete)
787
+ if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: complete)
788
+ after.lines("\n").map { |l| l.chomp('') }
789
+ else
790
+ before.map { |l| Reline::Unicode.escape_for_print(l) }
791
+ end
792
+ end
793
+
794
+ def editing_mode
795
+ @config.editing_mode
796
+ end
797
+
798
+ private def menu(list)
799
+ @menu_info = MenuInfo.new(list)
800
+ end
801
+
802
+ private def filter_normalize_candidates(target, list)
803
+ target = target.downcase if @config.completion_ignore_case
804
+ list.select do |item|
805
+ next unless item
806
+ unless Encoding.compatible?(target.encoding, item.encoding)
807
+ # Workaround for Readline test
808
+ if defined?(::Readline) && ::Readline == ::Reline
809
+ raise Encoding::CompatibilityError, "incompatible character encodings: #{target.encoding} and #{item.encoding}"
810
+ end
811
+ end
812
+
813
+ if @config.completion_ignore_case
814
+ item.downcase.start_with?(target)
815
+ else
816
+ item.start_with?(target)
817
+ end
818
+ end.map do |item|
819
+ item.unicode_normalize
820
+ rescue Encoding::CompatibilityError
821
+ item
822
+ end.uniq
823
+ end
824
+
825
+ private def perform_completion(preposing, target, postposing, quote, list)
826
+ candidates = filter_normalize_candidates(target, list)
827
+
828
+ case @completion_state
829
+ when CompletionState::PERFECT_MATCH
830
+ if @dig_perfect_match_proc
831
+ @dig_perfect_match_proc.call(@perfect_matched)
832
+ return
833
+ end
834
+ when CompletionState::MENU
835
+ menu(candidates)
836
+ return
837
+ when CompletionState::MENU_WITH_PERFECT_MATCH
838
+ menu(candidates)
839
+ @completion_state = CompletionState::PERFECT_MATCH
840
+ return
841
+ end
842
+
843
+ completed = Reline::Unicode.common_prefix(candidates, ignore_case: @config.completion_ignore_case)
844
+ return if completed.empty?
845
+
846
+ append_character = ''
847
+ if candidates.include?(completed)
848
+ if candidates.one?
849
+ append_character = quote || completion_append_character.to_s
850
+ @completion_state = CompletionState::PERFECT_MATCH
851
+ elsif @config.show_all_if_ambiguous
852
+ menu(candidates)
853
+ @completion_state = CompletionState::PERFECT_MATCH
854
+ else
855
+ @completion_state = CompletionState::MENU_WITH_PERFECT_MATCH
856
+ end
857
+ @perfect_matched = completed
858
+ else
859
+ @completion_state = CompletionState::MENU
860
+ menu(candidates) if @config.show_all_if_ambiguous
861
+ end
862
+ @buffer_of_lines[@line_index] = (preposing + completed + append_character + postposing).split("\n")[@line_index] || String.new(encoding: encoding)
863
+ line_to_pointer = (preposing + completed + append_character).split("\n")[@line_index] || String.new(encoding: encoding)
864
+ @byte_pointer = line_to_pointer.bytesize
865
+ end
866
+
867
+ def dialog_proc_scope_completion_journey_data
868
+ return nil unless @completion_journey_state
869
+ line_index = @completion_journey_state.line_index
870
+ pre_lines = @buffer_of_lines[0...line_index].map { |line| line + "\n" }
871
+ post_lines = @buffer_of_lines[(line_index + 1)..-1].map { |line| line + "\n" }
872
+ DialogProcScope::CompletionJourneyData.new(
873
+ pre_lines.join + @completion_journey_state.pre,
874
+ @completion_journey_state.post + post_lines.join,
875
+ @completion_journey_state.list,
876
+ @completion_journey_state.pointer
877
+ )
878
+ end
879
+
880
+ private def move_completed_list(direction)
881
+ @completion_journey_state ||= retrieve_completion_journey_state
882
+ return false unless @completion_journey_state
883
+
884
+ if (delta = { up: -1, down: +1 }[direction])
885
+ @completion_journey_state.pointer = (@completion_journey_state.pointer + delta) % @completion_journey_state.list.size
886
+ end
887
+ completed = @completion_journey_state.list[@completion_journey_state.pointer]
888
+ set_current_line(@completion_journey_state.pre + completed + @completion_journey_state.post, @completion_journey_state.pre.bytesize + completed.bytesize)
889
+ true
890
+ end
891
+
892
+ private def retrieve_completion_journey_state
893
+ preposing, target, postposing, quote = retrieve_completion_block
894
+ list = call_completion_proc(preposing, target, postposing, quote)
895
+ return unless list.is_a?(Array)
896
+
897
+ candidates = list.select{ |item| item.start_with?(target) }
898
+ return if candidates.empty?
899
+
900
+ pre = preposing.split("\n", -1).last || ''
901
+ post = postposing.split("\n", -1).first || ''
902
+ CompletionJourneyState.new(
903
+ @line_index, pre, target, post, [target] + candidates, 0
904
+ )
905
+ end
906
+
907
+ private def run_for_operators(key, method_symbol)
908
+ # Reject multibyte input (converted to ed_insert) in vi_command mode
909
+ return if method_symbol == :ed_insert && @config.editing_mode_is?(:vi_command) && !@waiting_proc
910
+
911
+ if ARGUMENT_DIGIT_METHODS.include?(method_symbol) && !@waiting_proc
912
+ wrap_method_call(method_symbol, key, false)
913
+ return
914
+ end
915
+
916
+ if @vi_waiting_operator
917
+ if @waiting_proc || VI_MOTIONS.include?(method_symbol)
918
+ old_byte_pointer = @byte_pointer
919
+ @vi_arg = (@vi_arg || 1) * @vi_waiting_operator_arg
920
+ wrap_method_call(method_symbol, key, true)
921
+ unless @waiting_proc
922
+ byte_pointer_diff = @byte_pointer - old_byte_pointer
923
+ @byte_pointer = old_byte_pointer
924
+ __send__(@vi_waiting_operator, byte_pointer_diff)
925
+ cleanup_waiting
926
+ end
927
+ else
928
+ # Ignores operator when not motion is given.
929
+ wrap_method_call(method_symbol, key, false)
930
+ cleanup_waiting
931
+ end
932
+ else
933
+ wrap_method_call(method_symbol, key, false)
934
+ end
935
+ @vi_arg = nil
936
+ @kill_ring.process
937
+ end
938
+
939
+ private def argumentable?(method_obj)
940
+ method_obj and method_obj.parameters.any? { |param| param[0] == :key and param[1] == :arg }
941
+ end
942
+
943
+ private def inclusive?(method_obj)
944
+ # If a motion method with the keyword argument "inclusive" follows the
945
+ # operator, it must contain the character at the cursor position.
946
+ method_obj and method_obj.parameters.any? { |param| param[0] == :key and param[1] == :inclusive }
947
+ end
948
+
949
+ def wrap_method_call(method_symbol, key, with_operator)
950
+ if @waiting_proc
951
+ @waiting_proc.call(key)
952
+ return
953
+ end
954
+
955
+ return unless respond_to?(method_symbol, true)
956
+ method_obj = method(method_symbol)
957
+ if @vi_arg and argumentable?(method_obj)
958
+ if inclusive?(method_obj)
959
+ method_obj.(key, arg: @vi_arg, inclusive: with_operator)
960
+ else
961
+ method_obj.(key, arg: @vi_arg)
962
+ end
963
+ else
964
+ if inclusive?(method_obj)
965
+ method_obj.(key, inclusive: with_operator)
966
+ else
967
+ method_obj.(key)
968
+ end
969
+ end
970
+ end
971
+
972
+ private def cleanup_waiting
973
+ @waiting_proc = nil
974
+ @vi_waiting_operator = nil
975
+ @vi_waiting_operator_arg = nil
976
+ @searching_prompt = nil
977
+ @drop_terminate_spaces = false
978
+ end
979
+
980
+ ARGUMENT_DIGIT_METHODS = %i[ed_digit vi_zero ed_argument_digit]
981
+ VI_WAITING_ACCEPT_METHODS = %i[vi_change_meta vi_delete_meta vi_yank ed_insert ed_argument_digit]
982
+
983
+ private def process_key(key, method_symbol)
984
+ if @waiting_proc
985
+ cleanup_waiting unless key.size == 1
986
+ end
987
+ if @vi_waiting_operator
988
+ cleanup_waiting unless VI_WAITING_ACCEPT_METHODS.include?(method_symbol) || VI_MOTIONS.include?(method_symbol)
989
+ end
990
+
991
+ process_insert(force: method_symbol != :ed_insert)
992
+
993
+ run_for_operators(key, method_symbol)
994
+ end
995
+
996
+ def update(key)
997
+ modified = input_key(key)
998
+ unless @in_pasting
999
+ scroll_into_view
1000
+ @just_cursor_moving = !modified
1001
+ update_dialogs(key)
1002
+ @just_cursor_moving = false
1003
+ end
1004
+ end
1005
+
1006
+ def input_key(key)
1007
+ old_buffer_of_lines = @buffer_of_lines.dup
1008
+ @config.reset_oneshot_key_bindings
1009
+ if key.char.nil?
1010
+ process_insert(force: true)
1011
+ @eof = buffer_empty?
1012
+ finish
1013
+ return
1014
+ end
1015
+ return if @dialogs.any? { |dialog| dialog.name == key.method_symbol }
1016
+
1017
+ @completion_occurs = false
1018
+
1019
+ process_key(key.char, key.method_symbol)
1020
+ if @config.editing_mode_is?(:vi_command) and @byte_pointer > 0 and @byte_pointer == current_line.bytesize
1021
+ byte_size = Reline::Unicode.get_prev_mbchar_size(@buffer_of_lines[@line_index], @byte_pointer)
1022
+ @byte_pointer -= byte_size
1023
+ end
1024
+
1025
+ @prev_action_state, @next_action_state = @next_action_state, NullActionState
1026
+
1027
+ unless @completion_occurs
1028
+ @completion_state = CompletionState::NORMAL
1029
+ @completion_journey_state = nil
1030
+ end
1031
+
1032
+ modified = old_buffer_of_lines != @buffer_of_lines
1033
+
1034
+ push_undo_redo(modified) unless @restoring
1035
+ @restoring = false
1036
+
1037
+ if @in_pasting
1038
+ clear_dialogs
1039
+ return
1040
+ end
1041
+
1042
+ if !@completion_occurs && modified && !@config.disable_completion && @config.autocompletion
1043
+ # Auto complete starts only when edited
1044
+ process_insert(force: true)
1045
+ @completion_journey_state = retrieve_completion_journey_state
1046
+ end
1047
+ modified
1048
+ end
1049
+
1050
+ MAX_UNDO_REDO_HISTORY_SIZE = 100
1051
+ def push_undo_redo(modified)
1052
+ if modified
1053
+ @undo_redo_history = @undo_redo_history[0..@undo_redo_index]
1054
+ @undo_redo_history.push([@buffer_of_lines.dup, @byte_pointer, @line_index])
1055
+ if @undo_redo_history.size > MAX_UNDO_REDO_HISTORY_SIZE
1056
+ @undo_redo_history.shift
1057
+ end
1058
+ @undo_redo_index = @undo_redo_history.size - 1
1059
+ else
1060
+ @undo_redo_history[@undo_redo_index] = [@buffer_of_lines.dup, @byte_pointer, @line_index]
1061
+ end
1062
+ end
1063
+
1064
+ def scroll_into_view
1065
+ _wrapped_cursor_x, wrapped_cursor_y = wrapped_cursor_position
1066
+ if wrapped_cursor_y < screen_scroll_top
1067
+ @scroll_partial_screen = wrapped_cursor_y
1068
+ end
1069
+ if wrapped_cursor_y >= screen_scroll_top + screen_height
1070
+ @scroll_partial_screen = wrapped_cursor_y - screen_height + 1
1071
+ end
1072
+ end
1073
+
1074
+ def call_completion_proc(pre, target, post, quote)
1075
+ Reline.core.instance_variable_set(:@completion_quote_character, quote)
1076
+ result = call_completion_proc_with_checking_args(pre, target, post)
1077
+ Reline.core.instance_variable_set(:@completion_quote_character, nil)
1078
+ result
1079
+ end
1080
+
1081
+ def call_completion_proc_with_checking_args(pre, target, post)
1082
+ if @completion_proc and target
1083
+ argnum = @completion_proc.parameters.inject(0) { |result, item|
1084
+ case item.first
1085
+ when :req, :opt
1086
+ result + 1
1087
+ when :rest
1088
+ break 3
1089
+ end
1090
+ }
1091
+ case argnum
1092
+ when 1
1093
+ result = @completion_proc.(target)
1094
+ when 2
1095
+ result = @completion_proc.(target, pre)
1096
+ when 3..Float::INFINITY
1097
+ result = @completion_proc.(target, pre, post)
1098
+ end
1099
+ end
1100
+ result
1101
+ end
1102
+
1103
+ private def process_auto_indent(line_index = @line_index, cursor_dependent: true, add_newline: false)
1104
+ return if @in_pasting
1105
+ return unless @auto_indent_proc
1106
+
1107
+ line = @buffer_of_lines[line_index]
1108
+ byte_pointer = cursor_dependent && @line_index == line_index ? @byte_pointer : line.bytesize
1109
+ new_indent = @auto_indent_proc.(@buffer_of_lines.take(line_index + 1).push(''), line_index, byte_pointer, add_newline)
1110
+ return unless new_indent
1111
+
1112
+ new_line = ' ' * new_indent + line.lstrip
1113
+ @buffer_of_lines[line_index] = new_line
1114
+ if @line_index == line_index
1115
+ indent_diff = new_line.bytesize - line.bytesize
1116
+ @byte_pointer = [@byte_pointer + indent_diff, 0].max
1117
+ end
1118
+ end
1119
+
1120
+ def line()
1121
+ @buffer_of_lines.join("\n") unless eof?
1122
+ end
1123
+
1124
+ def current_line
1125
+ @buffer_of_lines[@line_index]
1126
+ end
1127
+
1128
+ def set_current_line(line, byte_pointer = nil)
1129
+ cursor = current_byte_pointer_cursor
1130
+ @buffer_of_lines[@line_index] = line
1131
+ if byte_pointer
1132
+ @byte_pointer = byte_pointer
1133
+ else
1134
+ calculate_nearest_cursor(cursor)
1135
+ end
1136
+ process_auto_indent
1137
+ end
1138
+
1139
+ def retrieve_completion_block
1140
+ quote_characters = Reline.completer_quote_characters
1141
+ before = current_line.byteslice(0, @byte_pointer).grapheme_clusters
1142
+ quote = nil
1143
+ # Calculate closing quote when cursor is at the end of the line
1144
+ if current_line.bytesize == @byte_pointer && !quote_characters.empty?
1145
+ escaped = false
1146
+ before.each do |c|
1147
+ if escaped
1148
+ escaped = false
1149
+ next
1150
+ elsif c == '\\'
1151
+ escaped = true
1152
+ elsif quote
1153
+ quote = nil if c == quote
1154
+ elsif quote_characters.include?(c)
1155
+ quote = c
1156
+ end
1157
+ end
1158
+ end
1159
+
1160
+ word_break_characters = quote_characters + Reline.completer_word_break_characters
1161
+ break_index = before.rindex { |c| word_break_characters.include?(c) || quote_characters.include?(c) } || -1
1162
+ preposing = before.take(break_index + 1).join
1163
+ target = before.drop(break_index + 1).join
1164
+ postposing = current_line.byteslice(@byte_pointer, current_line.bytesize - @byte_pointer)
1165
+ lines = whole_lines
1166
+ if @line_index > 0
1167
+ preposing = lines[0..(@line_index - 1)].join("\n") + "\n" + preposing
1168
+ end
1169
+ if (lines.size - 1) > @line_index
1170
+ postposing = postposing + "\n" + lines[(@line_index + 1)..-1].join("\n")
1171
+ end
1172
+ [preposing.encode(encoding), target.encode(encoding), postposing.encode(encoding), quote&.encode(encoding)]
1173
+ end
1174
+
1175
+ def confirm_multiline_termination
1176
+ temp_buffer = @buffer_of_lines.dup
1177
+ @confirm_multiline_termination_proc.(temp_buffer.join("\n") + "\n")
1178
+ end
1179
+
1180
+ def insert_multiline_text(text)
1181
+ pre = @buffer_of_lines[@line_index].byteslice(0, @byte_pointer)
1182
+ post = @buffer_of_lines[@line_index].byteslice(@byte_pointer..)
1183
+ lines = (pre + Reline::Unicode.safe_encode(text, encoding).gsub(/\r\n?/, "\n") + post).split("\n", -1)
1184
+ lines << '' if lines.empty?
1185
+ @buffer_of_lines[@line_index, 1] = lines
1186
+ @line_index += lines.size - 1
1187
+ @byte_pointer = @buffer_of_lines[@line_index].bytesize - post.bytesize
1188
+ end
1189
+
1190
+ def insert_text(text)
1191
+ if @buffer_of_lines[@line_index].bytesize == @byte_pointer
1192
+ @buffer_of_lines[@line_index] += text
1193
+ else
1194
+ @buffer_of_lines[@line_index] = byteinsert(@buffer_of_lines[@line_index], @byte_pointer, text)
1195
+ end
1196
+ @byte_pointer += text.bytesize
1197
+ process_auto_indent
1198
+ end
1199
+
1200
+ def delete_text(start = nil, length = nil)
1201
+ if start.nil? and length.nil?
1202
+ if @buffer_of_lines.size == 1
1203
+ @buffer_of_lines[@line_index] = ''
1204
+ @byte_pointer = 0
1205
+ elsif @line_index == (@buffer_of_lines.size - 1) and @line_index > 0
1206
+ @buffer_of_lines.pop
1207
+ @line_index -= 1
1208
+ @byte_pointer = 0
1209
+ elsif @line_index < (@buffer_of_lines.size - 1)
1210
+ @buffer_of_lines.delete_at(@line_index)
1211
+ @byte_pointer = 0
1212
+ end
1213
+ elsif not start.nil? and not length.nil?
1214
+ if current_line
1215
+ before = current_line.byteslice(0, start)
1216
+ after = current_line.byteslice(start + length, current_line.bytesize)
1217
+ set_current_line(before + after)
1218
+ end
1219
+ elsif start.is_a?(Range)
1220
+ range = start
1221
+ first = range.first
1222
+ last = range.last
1223
+ last = current_line.bytesize - 1 if last > current_line.bytesize
1224
+ last += current_line.bytesize if last < 0
1225
+ first += current_line.bytesize if first < 0
1226
+ range = range.exclude_end? ? first...last : first..last
1227
+ line = current_line.bytes.reject.with_index{ |c, i| range.include?(i) }.map{ |c| c.chr(Encoding::ASCII_8BIT) }.join.force_encoding(encoding)
1228
+ set_current_line(line)
1229
+ else
1230
+ set_current_line(current_line.byteslice(0, start))
1231
+ end
1232
+ end
1233
+
1234
+ def byte_pointer=(val)
1235
+ @byte_pointer = val
1236
+ end
1237
+
1238
+ def whole_lines
1239
+ @buffer_of_lines.dup
1240
+ end
1241
+
1242
+ def whole_buffer
1243
+ whole_lines.join("\n")
1244
+ end
1245
+
1246
+ private def buffer_empty?
1247
+ current_line.empty? and @buffer_of_lines.size == 1
1248
+ end
1249
+
1250
+ def finished?
1251
+ @finished
1252
+ end
1253
+
1254
+ def finish
1255
+ @finished = true
1256
+ @config.reset
1257
+ end
1258
+
1259
+ private def byteslice!(str, byte_pointer, size)
1260
+ new_str = str.byteslice(0, byte_pointer)
1261
+ new_str << str.byteslice(byte_pointer + size, str.bytesize)
1262
+ [new_str, str.byteslice(byte_pointer, size)]
1263
+ end
1264
+
1265
+ private def byteinsert(str, byte_pointer, other)
1266
+ new_str = str.byteslice(0, byte_pointer)
1267
+ new_str << other
1268
+ new_str << str.byteslice(byte_pointer, str.bytesize)
1269
+ new_str
1270
+ end
1271
+
1272
+ private def calculate_width(str, allow_escape_code = false)
1273
+ Reline::Unicode.calculate_width(str, allow_escape_code)
1274
+ end
1275
+
1276
+ private def key_delete(key)
1277
+ if @config.editing_mode_is?(:vi_insert)
1278
+ ed_delete_next_char(key)
1279
+ elsif @config.editing_mode_is?(:emacs)
1280
+ em_delete(key)
1281
+ end
1282
+ end
1283
+
1284
+ private def key_newline(key)
1285
+ if @is_multiline
1286
+ next_line = current_line.byteslice(@byte_pointer, current_line.bytesize - @byte_pointer)
1287
+ cursor_line = current_line.byteslice(0, @byte_pointer)
1288
+ insert_new_line(cursor_line, next_line)
1289
+ end
1290
+ end
1291
+
1292
+ private def complete(_key)
1293
+ return if @config.disable_completion
1294
+
1295
+ process_insert(force: true)
1296
+ if @config.autocompletion
1297
+ @completion_state = CompletionState::NORMAL
1298
+ @completion_occurs = move_completed_list(:down)
1299
+ else
1300
+ @completion_journey_state = nil
1301
+ pre, target, post, quote = retrieve_completion_block
1302
+ result = call_completion_proc(pre, target, post, quote)
1303
+ if result.is_a?(Array)
1304
+ @completion_occurs = true
1305
+ perform_completion(pre, target, post, quote, result)
1306
+ end
1307
+ end
1308
+ end
1309
+
1310
+ private def completion_journey_move(direction)
1311
+ return if @config.disable_completion
1312
+
1313
+ process_insert(force: true)
1314
+ @completion_state = CompletionState::NORMAL
1315
+ @completion_occurs = move_completed_list(direction)
1316
+ end
1317
+
1318
+ private def menu_complete(_key)
1319
+ completion_journey_move(:down)
1320
+ end
1321
+
1322
+ private def menu_complete_backward(_key)
1323
+ completion_journey_move(:up)
1324
+ end
1325
+
1326
+ private def completion_journey_up(_key)
1327
+ completion_journey_move(:up) if @config.autocompletion
1328
+ end
1329
+
1330
+ # Editline:: +ed-unassigned+ This editor command always results in an error.
1331
+ # GNU Readline:: There is no corresponding macro.
1332
+ private def ed_unassigned(key) end # do nothing
1333
+
1334
+ private def process_insert(force: false)
1335
+ return if @continuous_insertion_buffer.empty? or (@in_pasting and not force)
1336
+ insert_text(@continuous_insertion_buffer)
1337
+ @continuous_insertion_buffer.clear
1338
+ end
1339
+
1340
+ # Editline:: +ed-insert+ (vi input: almost all; emacs: printable characters)
1341
+ # In insert mode, insert the input character left of the cursor
1342
+ # position. In replace mode, overwrite the character at the
1343
+ # cursor and move the cursor to the right by one character
1344
+ # position. Accept an argument to do this repeatedly. It is an
1345
+ # error if the input character is the NUL character (+Ctrl-@+).
1346
+ # Failure to enlarge the edit buffer also results in an error.
1347
+ # Editline:: +ed-digit+ (emacs: 0 to 9) If in argument input mode, append
1348
+ # the input digit to the argument being read. Otherwise, call
1349
+ # +ed-insert+. It is an error if the input character is not a
1350
+ # digit or if the existing argument is already greater than a
1351
+ # million.
1352
+ # GNU Readline:: +self-insert+ (a, b, A, 1, !, …) Insert yourself.
1353
+ private def ed_insert(str)
1354
+ begin
1355
+ str.encode(Encoding::UTF_8)
1356
+ rescue Encoding::UndefinedConversionError
1357
+ return
1358
+ end
1359
+ if @in_pasting
1360
+ @continuous_insertion_buffer << str
1361
+ return
1362
+ elsif not @continuous_insertion_buffer.empty?
1363
+ process_insert
1364
+ end
1365
+
1366
+ insert_text(str)
1367
+ end
1368
+ alias_method :self_insert, :ed_insert
1369
+
1370
+ private def ed_digit(key)
1371
+ if @vi_arg
1372
+ ed_argument_digit(key)
1373
+ else
1374
+ ed_insert(key)
1375
+ end
1376
+ end
1377
+
1378
+ private def insert_raw_char(str, arg: 1)
1379
+ arg.times do
1380
+ if str == "\C-j" or str == "\C-m"
1381
+ key_newline(str)
1382
+ elsif str != "\0"
1383
+ # Ignore NUL.
1384
+ ed_insert(str)
1385
+ end
1386
+ end
1387
+ end
1388
+
1389
+ private def ed_next_char(key, arg: 1)
1390
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
1391
+ if (@byte_pointer < current_line.bytesize)
1392
+ @byte_pointer += byte_size
1393
+ elsif @config.editing_mode_is?(:emacs) and @byte_pointer == current_line.bytesize and @line_index < @buffer_of_lines.size - 1
1394
+ @byte_pointer = 0
1395
+ @line_index += 1
1396
+ end
1397
+ arg -= 1
1398
+ ed_next_char(key, arg: arg) if arg > 0
1399
+ end
1400
+ alias_method :forward_char, :ed_next_char
1401
+
1402
+ private def ed_prev_char(key, arg: 1)
1403
+ if @byte_pointer > 0
1404
+ byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer)
1405
+ @byte_pointer -= byte_size
1406
+ elsif @config.editing_mode_is?(:emacs) and @byte_pointer == 0 and @line_index > 0
1407
+ @line_index -= 1
1408
+ @byte_pointer = current_line.bytesize
1409
+ end
1410
+ arg -= 1
1411
+ ed_prev_char(key, arg: arg) if arg > 0
1412
+ end
1413
+ alias_method :backward_char, :ed_prev_char
1414
+
1415
+ private def vi_first_print(key)
1416
+ @byte_pointer = Reline::Unicode.vi_first_print(current_line)
1417
+ end
1418
+
1419
+ private def ed_move_to_beg(key)
1420
+ @byte_pointer = 0
1421
+ end
1422
+ alias_method :beginning_of_line, :ed_move_to_beg
1423
+
1424
+ private def vi_zero(key)
1425
+ if @vi_arg
1426
+ ed_argument_digit(key)
1427
+ else
1428
+ ed_move_to_beg(key)
1429
+ end
1430
+ end
1431
+
1432
+ private def ed_move_to_end(key)
1433
+ @byte_pointer = current_line.bytesize
1434
+ end
1435
+ alias_method :end_of_line, :ed_move_to_end
1436
+
1437
+ private def generate_searcher(search_key)
1438
+ search_word = String.new(encoding: encoding)
1439
+ hit_pointer = nil
1440
+ lambda do |key|
1441
+ search_again = false
1442
+ case key
1443
+ when "\C-h", "\C-?"
1444
+ grapheme_clusters = search_word.grapheme_clusters
1445
+ if grapheme_clusters.size > 0
1446
+ grapheme_clusters.pop
1447
+ search_word = grapheme_clusters.join
1448
+ end
1449
+ when "\C-r", "\C-s"
1450
+ search_again = true if search_key == key
1451
+ search_key = key
1452
+ else
1453
+ search_word << key
1454
+ end
1455
+ hit = nil
1456
+ if not search_word.empty? and @line_backup_in_history&.include?(search_word)
1457
+ hit_pointer = Reline::HISTORY.size
1458
+ hit = @line_backup_in_history
1459
+ else
1460
+ if search_again
1461
+ if search_word.empty? and Reline.last_incremental_search
1462
+ search_word = Reline.last_incremental_search
1463
+ end
1464
+ if @history_pointer
1465
+ case search_key
1466
+ when "\C-r"
1467
+ history_pointer_base = 0
1468
+ history = Reline::HISTORY[0..(@history_pointer - 1)]
1469
+ when "\C-s"
1470
+ history_pointer_base = @history_pointer + 1
1471
+ history = Reline::HISTORY[(@history_pointer + 1)..-1]
1472
+ end
1473
+ else
1474
+ history_pointer_base = 0
1475
+ history = Reline::HISTORY
1476
+ end
1477
+ elsif @history_pointer
1478
+ case search_key
1479
+ when "\C-r"
1480
+ history_pointer_base = 0
1481
+ history = Reline::HISTORY[0..@history_pointer]
1482
+ when "\C-s"
1483
+ history_pointer_base = @history_pointer
1484
+ history = Reline::HISTORY[@history_pointer..-1]
1485
+ end
1486
+ else
1487
+ history_pointer_base = 0
1488
+ history = Reline::HISTORY
1489
+ end
1490
+ case search_key
1491
+ when "\C-r"
1492
+ hit_index = history.rindex { |item|
1493
+ item.include?(search_word)
1494
+ }
1495
+ when "\C-s"
1496
+ hit_index = history.index { |item|
1497
+ item.include?(search_word)
1498
+ }
1499
+ end
1500
+ if hit_index
1501
+ hit_pointer = history_pointer_base + hit_index
1502
+ hit = Reline::HISTORY[hit_pointer]
1503
+ end
1504
+ end
1505
+ case search_key
1506
+ when "\C-r"
1507
+ prompt_name = 'reverse-i-search'
1508
+ when "\C-s"
1509
+ prompt_name = 'i-search'
1510
+ end
1511
+ prompt_name = "failed #{prompt_name}" unless hit
1512
+ [search_word, prompt_name, hit_pointer]
1513
+ end
1514
+ end
1515
+
1516
+ private def incremental_search_history(key)
1517
+ backup = @buffer_of_lines.dup, @line_index, @byte_pointer, @history_pointer, @line_backup_in_history
1518
+ searcher = generate_searcher(key)
1519
+ @searching_prompt = "(reverse-i-search)`': "
1520
+ termination_keys = ["\C-j"]
1521
+ termination_keys.concat(@config.isearch_terminators.chars) if @config.isearch_terminators
1522
+ @waiting_proc = ->(k) {
1523
+ if k == "\C-g"
1524
+ # cancel search and restore buffer
1525
+ @buffer_of_lines, @line_index, @byte_pointer, @history_pointer, @line_backup_in_history = backup
1526
+ @searching_prompt = nil
1527
+ @waiting_proc = nil
1528
+ elsif !termination_keys.include?(k) && (k.match?(/[[:print:]]/) || k == "\C-h" || k == "\C-?" || k == "\C-r" || k == "\C-s")
1529
+ search_word, prompt_name, hit_pointer = searcher.call(k)
1530
+ Reline.last_incremental_search = search_word
1531
+ @searching_prompt = "(%s)`%s'" % [prompt_name, search_word]
1532
+ @searching_prompt += ': ' unless @is_multiline
1533
+ move_history(hit_pointer, line: :end, cursor: :end) if hit_pointer
1534
+ else
1535
+ # terminaton_keys and other keys will terminalte
1536
+ move_history(@history_pointer, line: :end, cursor: :start)
1537
+ @searching_prompt = nil
1538
+ @waiting_proc = nil
1539
+ end
1540
+ }
1541
+ end
1542
+
1543
+ private def vi_search_prev(key)
1544
+ incremental_search_history(key)
1545
+ end
1546
+ alias_method :reverse_search_history, :vi_search_prev
1547
+
1548
+ private def vi_search_next(key)
1549
+ incremental_search_history(key)
1550
+ end
1551
+ alias_method :forward_search_history, :vi_search_next
1552
+
1553
+ private def search_history(prefix, pointer_range)
1554
+ pointer_range.each do |pointer|
1555
+ lines = Reline::HISTORY[pointer].split("\n")
1556
+ lines.each_with_index do |line, index|
1557
+ return [pointer, index] if line.start_with?(prefix)
1558
+ end
1559
+ end
1560
+ nil
1561
+ end
1562
+
1563
+ private def ed_search_prev_history(key, arg: 1)
1564
+ substr = prev_action_state_value(:search_history) == :empty ? '' : current_line.byteslice(0, @byte_pointer)
1565
+ return if @history_pointer == 0
1566
+ return if @history_pointer.nil? && substr.empty? && !current_line.empty?
1567
+
1568
+ history_range = 0...(@history_pointer || Reline::HISTORY.size)
1569
+ h_pointer, line_index = search_history(substr, history_range.reverse_each)
1570
+ return unless h_pointer
1571
+ move_history(h_pointer, line: line_index || :start, cursor: substr.empty? ? :end : @byte_pointer)
1572
+ arg -= 1
1573
+ set_next_action_state(:search_history, :empty) if substr.empty?
1574
+ ed_search_prev_history(key, arg: arg) if arg > 0
1575
+ end
1576
+ alias_method :history_search_backward, :ed_search_prev_history
1577
+
1578
+ private def ed_search_next_history(key, arg: 1)
1579
+ substr = prev_action_state_value(:search_history) == :empty ? '' : current_line.byteslice(0, @byte_pointer)
1580
+ return if @history_pointer.nil?
1581
+
1582
+ history_range = @history_pointer + 1...Reline::HISTORY.size
1583
+ h_pointer, line_index = search_history(substr, history_range)
1584
+ return if h_pointer.nil? and not substr.empty?
1585
+
1586
+ move_history(h_pointer, line: line_index || :start, cursor: substr.empty? ? :end : @byte_pointer)
1587
+ arg -= 1
1588
+ set_next_action_state(:search_history, :empty) if substr.empty?
1589
+ ed_search_next_history(key, arg: arg) if arg > 0
1590
+ end
1591
+ alias_method :history_search_forward, :ed_search_next_history
1592
+
1593
+ private def move_history(history_pointer, line:, cursor:)
1594
+ history_pointer ||= Reline::HISTORY.size
1595
+ return if history_pointer < 0 || history_pointer > Reline::HISTORY.size
1596
+ old_history_pointer = @history_pointer || Reline::HISTORY.size
1597
+ if old_history_pointer == Reline::HISTORY.size
1598
+ @line_backup_in_history = whole_buffer
1599
+ else
1600
+ Reline::HISTORY[old_history_pointer] = whole_buffer
1601
+ end
1602
+ if history_pointer == Reline::HISTORY.size
1603
+ buf = @line_backup_in_history
1604
+ @history_pointer = @line_backup_in_history = nil
1605
+ else
1606
+ buf = Reline::HISTORY[history_pointer]
1607
+ @history_pointer = history_pointer
1608
+ end
1609
+ @buffer_of_lines = buf.split("\n")
1610
+ @buffer_of_lines = [String.new(encoding: encoding)] if @buffer_of_lines.empty?
1611
+ @line_index = line == :start ? 0 : line == :end ? @buffer_of_lines.size - 1 : line
1612
+ @byte_pointer = cursor == :start ? 0 : cursor == :end ? current_line.bytesize : cursor
1613
+ end
1614
+
1615
+ private def ed_prev_history(key, arg: 1)
1616
+ if @line_index > 0
1617
+ cursor = current_byte_pointer_cursor
1618
+ @line_index -= 1
1619
+ calculate_nearest_cursor(cursor)
1620
+ return
1621
+ end
1622
+ move_history(
1623
+ (@history_pointer || Reline::HISTORY.size) - 1,
1624
+ line: :end,
1625
+ cursor: @config.editing_mode_is?(:vi_command) ? :start : :end,
1626
+ )
1627
+ arg -= 1
1628
+ ed_prev_history(key, arg: arg) if arg > 0
1629
+ end
1630
+ alias_method :previous_history, :ed_prev_history
1631
+
1632
+ private def ed_next_history(key, arg: 1)
1633
+ if @line_index < (@buffer_of_lines.size - 1)
1634
+ cursor = current_byte_pointer_cursor
1635
+ @line_index += 1
1636
+ calculate_nearest_cursor(cursor)
1637
+ return
1638
+ end
1639
+ move_history(
1640
+ (@history_pointer || Reline::HISTORY.size) + 1,
1641
+ line: :start,
1642
+ cursor: @config.editing_mode_is?(:vi_command) ? :start : :end,
1643
+ )
1644
+ arg -= 1
1645
+ ed_next_history(key, arg: arg) if arg > 0
1646
+ end
1647
+ alias_method :next_history, :ed_next_history
1648
+
1649
+ private def ed_beginning_of_history(key)
1650
+ move_history(0, line: :end, cursor: :end)
1651
+ end
1652
+ alias_method :beginning_of_history, :ed_beginning_of_history
1653
+
1654
+ private def ed_end_of_history(key)
1655
+ move_history(Reline::HISTORY.size, line: :end, cursor: :end)
1656
+ end
1657
+ alias_method :end_of_history, :ed_end_of_history
1658
+
1659
+ private def ed_newline(key)
1660
+ process_insert(force: true)
1661
+ if @is_multiline
1662
+ if @config.editing_mode_is?(:vi_command)
1663
+ if @line_index < (@buffer_of_lines.size - 1)
1664
+ ed_next_history(key) # means cursor down
1665
+ else
1666
+ # should check confirm_multiline_termination to finish?
1667
+ finish
1668
+ end
1669
+ else
1670
+ if @line_index == @buffer_of_lines.size - 1 && confirm_multiline_termination
1671
+ finish
1672
+ else
1673
+ key_newline(key)
1674
+ end
1675
+ end
1676
+ else
1677
+ finish
1678
+ end
1679
+ end
1680
+
1681
+ private def ed_force_submit(_key)
1682
+ process_insert(force: true)
1683
+ finish
1684
+ end
1685
+
1686
+ private def em_delete_prev_char(key, arg: 1)
1687
+ arg.times do
1688
+ if @byte_pointer == 0 and @line_index > 0
1689
+ @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize
1690
+ @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index)
1691
+ @line_index -= 1
1692
+ elsif @byte_pointer > 0
1693
+ byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer)
1694
+ line, = byteslice!(current_line, @byte_pointer - byte_size, byte_size)
1695
+ set_current_line(line, @byte_pointer - byte_size)
1696
+ end
1697
+ end
1698
+ process_auto_indent
1699
+ end
1700
+ alias_method :backward_delete_char, :em_delete_prev_char
1701
+
1702
+ # Editline:: +ed-kill-line+ (vi command: +D+, +Ctrl-K+; emacs: +Ctrl-K+,
1703
+ # +Ctrl-U+) + Kill from the cursor to the end of the line.
1704
+ # GNU Readline:: +kill-line+ (+C-k+) Kill the text from point to the end of
1705
+ # the line. With a negative numeric argument, kill backward
1706
+ # from the cursor to the beginning of the current line.
1707
+ private def ed_kill_line(key)
1708
+ if current_line.bytesize > @byte_pointer
1709
+ line, deleted = byteslice!(current_line, @byte_pointer, current_line.bytesize - @byte_pointer)
1710
+ set_current_line(line, line.bytesize)
1711
+ @kill_ring.append(deleted)
1712
+ elsif @byte_pointer == current_line.bytesize and @buffer_of_lines.size > @line_index + 1
1713
+ set_current_line(current_line + @buffer_of_lines.delete_at(@line_index + 1), current_line.bytesize)
1714
+ end
1715
+ end
1716
+ alias_method :kill_line, :ed_kill_line
1717
+
1718
+ # Editline:: +vi_change_to_eol+ (vi command: +C+) + Kill and change from the cursor to the end of the line.
1719
+ private def vi_change_to_eol(key)
1720
+ ed_kill_line(key)
1721
+
1722
+ @config.editing_mode = :vi_insert
1723
+ end
1724
+
1725
+ # Editline:: +vi-kill-line-prev+ (vi: +Ctrl-U+) Delete the string from the
1726
+ # beginning of the edit buffer to the cursor and save it to the
1727
+ # cut buffer.
1728
+ # GNU Readline:: +unix-line-discard+ (+C-u+) Kill backward from the cursor
1729
+ # to the beginning of the current line.
1730
+ private def vi_kill_line_prev(key)
1731
+ if @byte_pointer > 0
1732
+ line, deleted = byteslice!(current_line, 0, @byte_pointer)
1733
+ set_current_line(line, 0)
1734
+ @kill_ring.append(deleted, true)
1735
+ end
1736
+ end
1737
+ alias_method :unix_line_discard, :vi_kill_line_prev
1738
+
1739
+ # Editline:: +em-kill-line+ (not bound) Delete the entire contents of the
1740
+ # edit buffer and save it to the cut buffer. +vi-kill-line-prev+
1741
+ # GNU Readline:: +kill-whole-line+ (not bound) Kill all characters on the
1742
+ # current line, no matter where point is.
1743
+ private def em_kill_line(key)
1744
+ if current_line.size > 0
1745
+ @kill_ring.append(current_line.dup, true)
1746
+ set_current_line('', 0)
1747
+ end
1748
+ end
1749
+ alias_method :kill_whole_line, :em_kill_line
1750
+
1751
+ private def em_delete(key)
1752
+ if buffer_empty? and key == "\C-d"
1753
+ @eof = true
1754
+ finish
1755
+ elsif @byte_pointer < current_line.bytesize
1756
+ splitted_last = current_line.byteslice(@byte_pointer, current_line.bytesize)
1757
+ mbchar = splitted_last.grapheme_clusters.first
1758
+ line, = byteslice!(current_line, @byte_pointer, mbchar.bytesize)
1759
+ set_current_line(line)
1760
+ elsif @byte_pointer == current_line.bytesize and @buffer_of_lines.size > @line_index + 1
1761
+ set_current_line(current_line + @buffer_of_lines.delete_at(@line_index + 1), current_line.bytesize)
1762
+ end
1763
+ end
1764
+ alias_method :delete_char, :em_delete
1765
+
1766
+ private def em_delete_or_list(key)
1767
+ if current_line.empty? or @byte_pointer < current_line.bytesize
1768
+ em_delete(key)
1769
+ elsif !@config.autocompletion # show completed list
1770
+ pre, target, post, quote = retrieve_completion_block
1771
+ result = call_completion_proc(pre, target, post, quote)
1772
+ if result.is_a?(Array)
1773
+ candidates = filter_normalize_candidates(target, result)
1774
+ menu(candidates)
1775
+ end
1776
+ end
1777
+ end
1778
+ alias_method :delete_char_or_list, :em_delete_or_list
1779
+
1780
+ private def em_yank(key)
1781
+ yanked = @kill_ring.yank
1782
+ insert_text(yanked) if yanked
1783
+ end
1784
+ alias_method :yank, :em_yank
1785
+
1786
+ private def em_yank_pop(key)
1787
+ yanked, prev_yank = @kill_ring.yank_pop
1788
+ if yanked
1789
+ line, = byteslice!(current_line, @byte_pointer - prev_yank.bytesize, prev_yank.bytesize)
1790
+ set_current_line(line, @byte_pointer - prev_yank.bytesize)
1791
+ insert_text(yanked)
1792
+ end
1793
+ end
1794
+ alias_method :yank_pop, :em_yank_pop
1795
+
1796
+ private def ed_clear_screen(key)
1797
+ Reline::IOGate.clear_screen
1798
+ @screen_size = Reline::IOGate.get_screen_size
1799
+ @rendered_screen.base_y = 0
1800
+ clear_rendered_screen_cache
1801
+ end
1802
+ alias_method :clear_screen, :ed_clear_screen
1803
+
1804
+ private def em_next_word(key)
1805
+ if current_line.bytesize > @byte_pointer
1806
+ byte_size = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
1807
+ @byte_pointer += byte_size
1808
+ end
1809
+ end
1810
+ alias_method :forward_word, :em_next_word
1811
+
1812
+ private def ed_prev_word(key)
1813
+ if @byte_pointer > 0
1814
+ byte_size = Reline::Unicode.em_backward_word(current_line, @byte_pointer)
1815
+ @byte_pointer -= byte_size
1816
+ end
1817
+ end
1818
+ alias_method :backward_word, :ed_prev_word
1819
+
1820
+ private def em_delete_next_word(key)
1821
+ if current_line.bytesize > @byte_pointer
1822
+ byte_size = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
1823
+ line, word = byteslice!(current_line, @byte_pointer, byte_size)
1824
+ set_current_line(line)
1825
+ @kill_ring.append(word)
1826
+ end
1827
+ end
1828
+ alias_method :kill_word, :em_delete_next_word
1829
+
1830
+ private def ed_delete_prev_word(key)
1831
+ if @byte_pointer > 0
1832
+ byte_size = Reline::Unicode.em_backward_word(current_line, @byte_pointer)
1833
+ line, word = byteslice!(current_line, @byte_pointer - byte_size, byte_size)
1834
+ set_current_line(line, @byte_pointer - byte_size)
1835
+ @kill_ring.append(word, true)
1836
+ end
1837
+ end
1838
+ alias_method :backward_kill_word, :ed_delete_prev_word
1839
+
1840
+ private def ed_transpose_chars(key)
1841
+ if @byte_pointer > 0
1842
+ if @byte_pointer < current_line.bytesize
1843
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
1844
+ @byte_pointer += byte_size
1845
+ end
1846
+ back1_byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer)
1847
+ if (@byte_pointer - back1_byte_size) > 0
1848
+ back2_byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer - back1_byte_size)
1849
+ back2_pointer = @byte_pointer - back1_byte_size - back2_byte_size
1850
+ line, back2_mbchar = byteslice!(current_line, back2_pointer, back2_byte_size)
1851
+ set_current_line(byteinsert(line, @byte_pointer - back2_byte_size, back2_mbchar))
1852
+ end
1853
+ end
1854
+ end
1855
+ alias_method :transpose_chars, :ed_transpose_chars
1856
+
1857
+ private def ed_transpose_words(key)
1858
+ left_word_start, middle_start, right_word_start, after_start = Reline::Unicode.ed_transpose_words(current_line, @byte_pointer)
1859
+ before = current_line.byteslice(0, left_word_start)
1860
+ left_word = current_line.byteslice(left_word_start, middle_start - left_word_start)
1861
+ middle = current_line.byteslice(middle_start, right_word_start - middle_start)
1862
+ right_word = current_line.byteslice(right_word_start, after_start - right_word_start)
1863
+ after = current_line.byteslice(after_start, current_line.bytesize - after_start)
1864
+ return if left_word.empty? or right_word.empty?
1865
+ from_head_to_left_word = before + right_word + middle + left_word
1866
+ set_current_line(from_head_to_left_word + after, from_head_to_left_word.bytesize)
1867
+ end
1868
+ alias_method :transpose_words, :ed_transpose_words
1869
+
1870
+ private def em_capitol_case(key)
1871
+ if current_line.bytesize > @byte_pointer
1872
+ byte_size, new_str = Reline::Unicode.em_forward_word_with_capitalization(current_line, @byte_pointer)
1873
+ before = current_line.byteslice(0, @byte_pointer)
1874
+ after = current_line.byteslice((@byte_pointer + byte_size)..-1)
1875
+ set_current_line(before + new_str + after, @byte_pointer + new_str.bytesize)
1876
+ end
1877
+ end
1878
+ alias_method :capitalize_word, :em_capitol_case
1879
+
1880
+ private def em_lower_case(key)
1881
+ if current_line.bytesize > @byte_pointer
1882
+ byte_size = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
1883
+ part = current_line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
1884
+ mbchar =~ /[A-Z]/ ? mbchar.downcase : mbchar
1885
+ }.join
1886
+ rest = current_line.byteslice((@byte_pointer + byte_size)..-1)
1887
+ line = current_line.byteslice(0, @byte_pointer) + part
1888
+ set_current_line(line + rest, line.bytesize)
1889
+ end
1890
+ end
1891
+ alias_method :downcase_word, :em_lower_case
1892
+
1893
+ private def em_upper_case(key)
1894
+ if current_line.bytesize > @byte_pointer
1895
+ byte_size = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
1896
+ part = current_line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
1897
+ mbchar =~ /[a-z]/ ? mbchar.upcase : mbchar
1898
+ }.join
1899
+ rest = current_line.byteslice((@byte_pointer + byte_size)..-1)
1900
+ line = current_line.byteslice(0, @byte_pointer) + part
1901
+ set_current_line(line + rest, line.bytesize)
1902
+ end
1903
+ end
1904
+ alias_method :upcase_word, :em_upper_case
1905
+
1906
+ private def em_kill_region(key)
1907
+ if @byte_pointer > 0
1908
+ byte_size = Reline::Unicode.em_big_backward_word(current_line, @byte_pointer)
1909
+ line, deleted = byteslice!(current_line, @byte_pointer - byte_size, byte_size)
1910
+ set_current_line(line, @byte_pointer - byte_size)
1911
+ @kill_ring.append(deleted, true)
1912
+ end
1913
+ end
1914
+ alias_method :unix_word_rubout, :em_kill_region
1915
+
1916
+ private def copy_for_vi(text)
1917
+ if @config.editing_mode_is?(:vi_insert) or @config.editing_mode_is?(:vi_command)
1918
+ @vi_clipboard = text
1919
+ end
1920
+ end
1921
+
1922
+ private def vi_insert(key)
1923
+ @config.editing_mode = :vi_insert
1924
+ end
1925
+
1926
+ private def vi_add(key)
1927
+ @config.editing_mode = :vi_insert
1928
+ ed_next_char(key)
1929
+ end
1930
+
1931
+ private def vi_command_mode(key)
1932
+ ed_prev_char(key)
1933
+ @config.editing_mode = :vi_command
1934
+ end
1935
+ alias_method :vi_movement_mode, :vi_command_mode
1936
+
1937
+ private def vi_next_word(key, arg: 1)
1938
+ if current_line.bytesize > @byte_pointer
1939
+ byte_size = Reline::Unicode.vi_forward_word(current_line, @byte_pointer, @drop_terminate_spaces)
1940
+ @byte_pointer += byte_size
1941
+ end
1942
+ arg -= 1
1943
+ vi_next_word(key, arg: arg) if arg > 0
1944
+ end
1945
+
1946
+ private def vi_prev_word(key, arg: 1)
1947
+ if @byte_pointer > 0
1948
+ byte_size = Reline::Unicode.vi_backward_word(current_line, @byte_pointer)
1949
+ @byte_pointer -= byte_size
1950
+ end
1951
+ arg -= 1
1952
+ vi_prev_word(key, arg: arg) if arg > 0
1953
+ end
1954
+
1955
+ private def vi_end_word(key, arg: 1, inclusive: false)
1956
+ if current_line.bytesize > @byte_pointer
1957
+ byte_size = Reline::Unicode.vi_forward_end_word(current_line, @byte_pointer)
1958
+ @byte_pointer += byte_size
1959
+ end
1960
+ arg -= 1
1961
+ if inclusive and arg.zero?
1962
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
1963
+ if byte_size > 0
1964
+ @byte_pointer += byte_size
1965
+ end
1966
+ end
1967
+ vi_end_word(key, arg: arg) if arg > 0
1968
+ end
1969
+
1970
+ private def vi_next_big_word(key, arg: 1)
1971
+ if current_line.bytesize > @byte_pointer
1972
+ byte_size = Reline::Unicode.vi_big_forward_word(current_line, @byte_pointer)
1973
+ @byte_pointer += byte_size
1974
+ end
1975
+ arg -= 1
1976
+ vi_next_big_word(key, arg: arg) if arg > 0
1977
+ end
1978
+
1979
+ private def vi_prev_big_word(key, arg: 1)
1980
+ if @byte_pointer > 0
1981
+ byte_size = Reline::Unicode.vi_big_backward_word(current_line, @byte_pointer)
1982
+ @byte_pointer -= byte_size
1983
+ end
1984
+ arg -= 1
1985
+ vi_prev_big_word(key, arg: arg) if arg > 0
1986
+ end
1987
+
1988
+ private def vi_end_big_word(key, arg: 1, inclusive: false)
1989
+ if current_line.bytesize > @byte_pointer
1990
+ byte_size = Reline::Unicode.vi_big_forward_end_word(current_line, @byte_pointer)
1991
+ @byte_pointer += byte_size
1992
+ end
1993
+ arg -= 1
1994
+ if inclusive and arg.zero?
1995
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
1996
+ if byte_size > 0
1997
+ @byte_pointer += byte_size
1998
+ end
1999
+ end
2000
+ vi_end_big_word(key, arg: arg) if arg > 0
2001
+ end
2002
+
2003
+ private def vi_delete_prev_char(key)
2004
+ if @byte_pointer == 0 and @line_index > 0
2005
+ @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize
2006
+ @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index)
2007
+ @line_index -= 1
2008
+ process_auto_indent cursor_dependent: false
2009
+ elsif @byte_pointer > 0
2010
+ byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer)
2011
+ @byte_pointer -= byte_size
2012
+ line, _ = byteslice!(current_line, @byte_pointer, byte_size)
2013
+ set_current_line(line)
2014
+ end
2015
+ end
2016
+
2017
+ private def vi_insert_at_bol(key)
2018
+ ed_move_to_beg(key)
2019
+ @config.editing_mode = :vi_insert
2020
+ end
2021
+
2022
+ private def vi_add_at_eol(key)
2023
+ ed_move_to_end(key)
2024
+ @config.editing_mode = :vi_insert
2025
+ end
2026
+
2027
+ private def ed_delete_prev_char(key, arg: 1)
2028
+ deleted = +''
2029
+ arg.times do
2030
+ if @byte_pointer > 0
2031
+ byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer)
2032
+ @byte_pointer -= byte_size
2033
+ line, mbchar = byteslice!(current_line, @byte_pointer, byte_size)
2034
+ set_current_line(line)
2035
+ deleted.prepend(mbchar)
2036
+ end
2037
+ end
2038
+ copy_for_vi(deleted)
2039
+ end
2040
+
2041
+ private def vi_change_meta(key, arg: nil)
2042
+ if @vi_waiting_operator
2043
+ set_current_line('', 0) if @vi_waiting_operator == :vi_change_meta_confirm && arg.nil?
2044
+ @vi_waiting_operator = nil
2045
+ @vi_waiting_operator_arg = nil
2046
+ else
2047
+ @drop_terminate_spaces = true
2048
+ @vi_waiting_operator = :vi_change_meta_confirm
2049
+ @vi_waiting_operator_arg = arg || 1
2050
+ end
2051
+ end
2052
+
2053
+ private def vi_change_meta_confirm(byte_pointer_diff)
2054
+ vi_delete_meta_confirm(byte_pointer_diff)
2055
+ @config.editing_mode = :vi_insert
2056
+ @drop_terminate_spaces = false
2057
+ end
2058
+
2059
+ private def vi_delete_meta(key, arg: nil)
2060
+ if @vi_waiting_operator
2061
+ set_current_line('', 0) if @vi_waiting_operator == :vi_delete_meta_confirm && arg.nil?
2062
+ @vi_waiting_operator = nil
2063
+ @vi_waiting_operator_arg = nil
2064
+ else
2065
+ @vi_waiting_operator = :vi_delete_meta_confirm
2066
+ @vi_waiting_operator_arg = arg || 1
2067
+ end
2068
+ end
2069
+
2070
+ private def vi_delete_meta_confirm(byte_pointer_diff)
2071
+ if byte_pointer_diff > 0
2072
+ line, cut = byteslice!(current_line, @byte_pointer, byte_pointer_diff)
2073
+ elsif byte_pointer_diff < 0
2074
+ line, cut = byteslice!(current_line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2075
+ else
2076
+ return
2077
+ end
2078
+ copy_for_vi(cut)
2079
+ set_current_line(line, @byte_pointer + (byte_pointer_diff < 0 ? byte_pointer_diff : 0))
2080
+ end
2081
+
2082
+ private def vi_yank(key, arg: nil)
2083
+ if @vi_waiting_operator
2084
+ copy_for_vi(current_line) if @vi_waiting_operator == :vi_yank_confirm && arg.nil?
2085
+ @vi_waiting_operator = nil
2086
+ @vi_waiting_operator_arg = nil
2087
+ else
2088
+ @vi_waiting_operator = :vi_yank_confirm
2089
+ @vi_waiting_operator_arg = arg || 1
2090
+ end
2091
+ end
2092
+
2093
+ private def vi_yank_confirm(byte_pointer_diff)
2094
+ if byte_pointer_diff > 0
2095
+ cut = current_line.byteslice(@byte_pointer, byte_pointer_diff)
2096
+ elsif byte_pointer_diff < 0
2097
+ cut = current_line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff)
2098
+ else
2099
+ return
2100
+ end
2101
+ copy_for_vi(cut)
2102
+ end
2103
+
2104
+ private def vi_list_or_eof(key)
2105
+ if buffer_empty?
2106
+ @eof = true
2107
+ finish
2108
+ else
2109
+ ed_newline(key)
2110
+ end
2111
+ end
2112
+ alias_method :vi_end_of_transmission, :vi_list_or_eof
2113
+ alias_method :vi_eof_maybe, :vi_list_or_eof
2114
+
2115
+ private def ed_delete_next_char(key, arg: 1)
2116
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
2117
+ unless current_line.empty? || byte_size == 0
2118
+ line, mbchar = byteslice!(current_line, @byte_pointer, byte_size)
2119
+ copy_for_vi(mbchar)
2120
+ if @byte_pointer > 0 && current_line.bytesize == @byte_pointer + byte_size
2121
+ byte_size = Reline::Unicode.get_prev_mbchar_size(line, @byte_pointer)
2122
+ set_current_line(line, @byte_pointer - byte_size)
2123
+ else
2124
+ set_current_line(line, @byte_pointer)
2125
+ end
2126
+ end
2127
+ arg -= 1
2128
+ ed_delete_next_char(key, arg: arg) if arg > 0
2129
+ end
2130
+
2131
+ private def vi_to_history_line(key)
2132
+ if Reline::HISTORY.empty?
2133
+ return
2134
+ end
2135
+ move_history(0, line: :start, cursor: :start)
2136
+ end
2137
+
2138
+ private def vi_histedit(key)
2139
+ path = Tempfile.open { |fp|
2140
+ fp.write whole_lines.join("\n")
2141
+ fp.path
2142
+ }
2143
+ system("#{ENV['EDITOR']} #{path}")
2144
+ @buffer_of_lines = File.read(path).split("\n")
2145
+ @buffer_of_lines = [String.new(encoding: encoding)] if @buffer_of_lines.empty?
2146
+ @line_index = 0
2147
+ finish
2148
+ end
2149
+
2150
+ private def vi_paste_prev(key, arg: 1)
2151
+ if @vi_clipboard.size > 0
2152
+ cursor_point = @vi_clipboard.grapheme_clusters[0..-2].join
2153
+ set_current_line(byteinsert(current_line, @byte_pointer, @vi_clipboard), @byte_pointer + cursor_point.bytesize)
2154
+ end
2155
+ arg -= 1
2156
+ vi_paste_prev(key, arg: arg) if arg > 0
2157
+ end
2158
+
2159
+ private def vi_paste_next(key, arg: 1)
2160
+ if @vi_clipboard.size > 0
2161
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
2162
+ line = byteinsert(current_line, @byte_pointer + byte_size, @vi_clipboard)
2163
+ set_current_line(line, @byte_pointer + @vi_clipboard.bytesize)
2164
+ end
2165
+ arg -= 1
2166
+ vi_paste_next(key, arg: arg) if arg > 0
2167
+ end
2168
+
2169
+ private def ed_argument_digit(key)
2170
+ # key is expected to be `ESC digit` or `digit`
2171
+ num = key[/\d/].to_i
2172
+ @vi_arg = (@vi_arg || 0) * 10 + num
2173
+ end
2174
+
2175
+ private def vi_to_column(key, arg: 0)
2176
+ # Implementing behavior of vi, not Readline's vi-mode.
2177
+ @byte_pointer, = current_line.grapheme_clusters.inject([0, 0]) { |(total_byte_size, total_width), gc|
2178
+ mbchar_width = Reline::Unicode.get_mbchar_width(gc)
2179
+ break [total_byte_size, total_width] if (total_width + mbchar_width) >= arg
2180
+ [total_byte_size + gc.bytesize, total_width + mbchar_width]
2181
+ }
2182
+ end
2183
+
2184
+ private def vi_replace_char(key, arg: 1)
2185
+ @waiting_proc = ->(k) {
2186
+ if arg == 1
2187
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
2188
+ before = current_line.byteslice(0, @byte_pointer)
2189
+ remaining_point = @byte_pointer + byte_size
2190
+ after = current_line.byteslice(remaining_point, current_line.bytesize - remaining_point)
2191
+ set_current_line(before + k + after)
2192
+ @waiting_proc = nil
2193
+ elsif arg > 1
2194
+ byte_size = 0
2195
+ arg.times do
2196
+ byte_size += Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer + byte_size)
2197
+ end
2198
+ before = current_line.byteslice(0, @byte_pointer)
2199
+ remaining_point = @byte_pointer + byte_size
2200
+ after = current_line.byteslice(remaining_point, current_line.bytesize - remaining_point)
2201
+ replaced = k * arg
2202
+ set_current_line(before + replaced + after, @byte_pointer + replaced.bytesize)
2203
+ @waiting_proc = nil
2204
+ end
2205
+ }
2206
+ end
2207
+
2208
+ private def vi_next_char(key, arg: 1, inclusive: false)
2209
+ @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, inclusive: inclusive) }
2210
+ end
2211
+
2212
+ private def vi_to_next_char(key, arg: 1, inclusive: false)
2213
+ @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, need_prev_char: true, inclusive: inclusive) }
2214
+ end
2215
+
2216
+ private def search_next_char(key, arg, need_prev_char: false, inclusive: false)
2217
+ prev_total = nil
2218
+ total = nil
2219
+ found = false
2220
+ current_line.byteslice(@byte_pointer..-1).grapheme_clusters.each do |mbchar|
2221
+ # total has [byte_size, cursor]
2222
+ unless total
2223
+ # skip cursor point
2224
+ width = Reline::Unicode.get_mbchar_width(mbchar)
2225
+ total = [mbchar.bytesize, width]
2226
+ else
2227
+ if key == mbchar
2228
+ arg -= 1
2229
+ if arg.zero?
2230
+ found = true
2231
+ break
2232
+ end
2233
+ end
2234
+ width = Reline::Unicode.get_mbchar_width(mbchar)
2235
+ prev_total = total
2236
+ total = [total.first + mbchar.bytesize, total.last + width]
2237
+ end
2238
+ end
2239
+ if not need_prev_char and found and total
2240
+ byte_size, _ = total
2241
+ @byte_pointer += byte_size
2242
+ elsif need_prev_char and found and prev_total
2243
+ byte_size, _ = prev_total
2244
+ @byte_pointer += byte_size
2245
+ end
2246
+ if inclusive
2247
+ byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
2248
+ if byte_size > 0
2249
+ @byte_pointer += byte_size
2250
+ end
2251
+ end
2252
+ @waiting_proc = nil
2253
+ end
2254
+
2255
+ private def vi_prev_char(key, arg: 1)
2256
+ @waiting_proc = ->(key_for_proc) { search_prev_char(key_for_proc, arg) }
2257
+ end
2258
+
2259
+ private def vi_to_prev_char(key, arg: 1)
2260
+ @waiting_proc = ->(key_for_proc) { search_prev_char(key_for_proc, arg, true) }
2261
+ end
2262
+
2263
+ private def search_prev_char(key, arg, need_next_char = false)
2264
+ prev_total = nil
2265
+ total = nil
2266
+ found = false
2267
+ current_line.byteslice(0..@byte_pointer).grapheme_clusters.reverse_each do |mbchar|
2268
+ # total has [byte_size, cursor]
2269
+ unless total
2270
+ # skip cursor point
2271
+ width = Reline::Unicode.get_mbchar_width(mbchar)
2272
+ total = [mbchar.bytesize, width]
2273
+ else
2274
+ if key == mbchar
2275
+ arg -= 1
2276
+ if arg.zero?
2277
+ found = true
2278
+ break
2279
+ end
2280
+ end
2281
+ width = Reline::Unicode.get_mbchar_width(mbchar)
2282
+ prev_total = total
2283
+ total = [total.first + mbchar.bytesize, total.last + width]
2284
+ end
2285
+ end
2286
+ if not need_next_char and found and total
2287
+ byte_size, _ = total
2288
+ @byte_pointer -= byte_size
2289
+ elsif need_next_char and found and prev_total
2290
+ byte_size, _ = prev_total
2291
+ @byte_pointer -= byte_size
2292
+ end
2293
+ @waiting_proc = nil
2294
+ end
2295
+
2296
+ private def vi_join_lines(key, arg: 1)
2297
+ if @buffer_of_lines.size > @line_index + 1
2298
+ next_line = @buffer_of_lines.delete_at(@line_index + 1).lstrip
2299
+ set_current_line(current_line + ' ' + next_line, current_line.bytesize)
2300
+ end
2301
+ arg -= 1
2302
+ vi_join_lines(key, arg: arg) if arg > 0
2303
+ end
2304
+
2305
+ private def em_set_mark(key)
2306
+ @mark_pointer = [@byte_pointer, @line_index]
2307
+ end
2308
+ alias_method :set_mark, :em_set_mark
2309
+
2310
+ private def em_exchange_mark(key)
2311
+ return unless @mark_pointer
2312
+ new_pointer = [@byte_pointer, @line_index]
2313
+ @byte_pointer, @line_index = @mark_pointer
2314
+ @mark_pointer = new_pointer
2315
+ end
2316
+ alias_method :exchange_point_and_mark, :em_exchange_mark
2317
+
2318
+ private def emacs_editing_mode(key)
2319
+ @config.editing_mode = :emacs
2320
+ end
2321
+
2322
+ private def vi_editing_mode(key)
2323
+ @config.editing_mode = :vi_insert
2324
+ end
2325
+
2326
+ private def move_undo_redo(direction)
2327
+ @restoring = true
2328
+ return unless (0..@undo_redo_history.size - 1).cover?(@undo_redo_index + direction)
2329
+
2330
+ @undo_redo_index += direction
2331
+ buffer_of_lines, byte_pointer, line_index = @undo_redo_history[@undo_redo_index]
2332
+ @buffer_of_lines = buffer_of_lines.dup
2333
+ @line_index = line_index
2334
+ @byte_pointer = byte_pointer
2335
+ end
2336
+
2337
+ private def undo(_key)
2338
+ move_undo_redo(-1)
2339
+ end
2340
+
2341
+ private def redo(_key)
2342
+ move_undo_redo(+1)
2343
+ end
2344
+
2345
+ private def prev_action_state_value(type)
2346
+ @prev_action_state[0] == type ? @prev_action_state[1] : nil
2347
+ end
2348
+
2349
+ private def set_next_action_state(type, value)
2350
+ @next_action_state = [type, value]
2351
+ end
2352
+
2353
+ private def re_read_init_file(_key)
2354
+ @config.reload
2355
+ end
2356
+ end