notemacs.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612
  1. # -*- coding: utf-8 -*-
  2. # *****************************************************************************
  3. # Copyright (C) 2003-2006 Gary Bishop.
  4. # Copyright (C) 2006-2020 Jorgen Stenarson. <jorgen.stenarson@bostream.nu>
  5. # Copyright (C) 2020 Bassem Girgis. <brgirgis@gmail.com>
  6. #
  7. # Distributed under the terms of the BSD License. The full license is in
  8. # the file COPYING, distributed as part of this software.
  9. # *****************************************************************************
  10. import os
  11. import pyreadline3.lineeditor.history as history
  12. import pyreadline3.lineeditor.lineobj as lineobj
  13. import pyreadline3.logger as logger
  14. from pyreadline3.logger import log
  15. from . import basemode
  16. class NotEmacsMode(basemode.BaseMode):
  17. mode = "notemacs"
  18. def __init__(self, rlobj):
  19. super().__init__(rlobj)
  20. def __repr__(self):
  21. return "<NotEmacsMode>"
  22. def _readline_from_keyboard(self):
  23. c = self.console
  24. while True:
  25. self._update_line()
  26. event = c.getkeypress()
  27. if self.next_meta:
  28. self.next_meta = False
  29. control, meta, shift, code = event.keyinfo
  30. event.keyinfo = (control, True, shift, code)
  31. # Process exit keys. Only exit on empty line
  32. if event.keyinfo in self.exit_dispatch:
  33. if lineobj.EndOfLine(self.l_buffer) == 0:
  34. raise EOFError
  35. dispatch_func = self.key_dispatch.get(event.keyinfo, self.self_insert)
  36. log("readline from keyboard:%s" % (event.keyinfo,))
  37. r = None
  38. if dispatch_func:
  39. r = dispatch_func(event)
  40. self.l_buffer.push_undo()
  41. self.previous_func = dispatch_func
  42. if r:
  43. self._update_line()
  44. break
  45. def readline(self, prompt=""):
  46. """Try to act like GNU readline."""
  47. # handle startup_hook
  48. if self.first_prompt:
  49. self.first_prompt = False
  50. if self.startup_hook:
  51. try:
  52. self.startup_hook()
  53. except BaseException:
  54. print("startup hook failed")
  55. traceback.print_exc()
  56. c = self.console
  57. self.l_buffer.reset_line()
  58. self.prompt = prompt
  59. self._print_prompt()
  60. if self.pre_input_hook:
  61. try:
  62. self.pre_input_hook()
  63. except BaseException:
  64. print("pre_input_hook failed")
  65. traceback.print_exc()
  66. self.pre_input_hook = None
  67. log("in readline: %s" % self.paste_line_buffer)
  68. if len(self.paste_line_buffer) > 0:
  69. self.l_buffer = lineobj.ReadlineTextBuffer(self.paste_line_buffer[0])
  70. self._update_line()
  71. self.paste_line_buffer = self.paste_line_buffer[1:]
  72. c.write("\r\n")
  73. else:
  74. self._readline_from_keyboard()
  75. c.write("\r\n")
  76. self.add_history(self.l_buffer.copy())
  77. log("returning(%s)" % self.l_buffer.get_line_text())
  78. return self.l_buffer.get_line_text() + "\n"
  79. # Methods below here are bindable emacs functions
  80. def beginning_of_line(self, e): # (C-a)
  81. """Move to the start of the current line."""
  82. self.l_buffer.beginning_of_line()
  83. def end_of_line(self, e): # (C-e)
  84. """Move to the end of the line."""
  85. self.l_buffer.end_of_line()
  86. def forward_char(self, e): # (C-f)
  87. """Move forward a character."""
  88. self.l_buffer.forward_char()
  89. def backward_char(self, e): # (C-b)
  90. """Move back a character."""
  91. self.l_buffer.backward_char()
  92. def forward_word(self, e): # (M-f)
  93. """Move forward to the end of the next word. Words are composed of
  94. letters and digits."""
  95. self.l_buffer.forward_word()
  96. def backward_word(self, e): # (M-b)
  97. """Move back to the start of the current or previous word. Words are
  98. composed of letters and digits."""
  99. self.l_buffer.backward_word()
  100. def clear_screen(self, e): # (C-l)
  101. """Clear the screen and redraw the current line, leaving the current
  102. line at the top of the screen."""
  103. self.console.page()
  104. def redraw_current_line(self, e): # ()
  105. """Refresh the current line. By default, this is unbound."""
  106. pass
  107. def accept_line(self, e): # (Newline or Return)
  108. """Accept the line regardless of where the cursor is. If this line
  109. is non-empty, it may be added to the history list for future recall
  110. with add_history(). If this line is a modified history line, the
  111. history line is restored to its original state."""
  112. return True
  113. # History commands
  114. def previous_history(self, e): # (C-p)
  115. """Move back through the history list, fetching the previous command."""
  116. self._history.previous_history(self.l_buffer)
  117. def next_history(self, e): # (C-n)
  118. """Move forward through the history list, fetching the next command."""
  119. self._history.next_history(self.l_buffer)
  120. def beginning_of_history(self, e): # (M-<)
  121. """Move to the first line in the history."""
  122. self._history.beginning_of_history()
  123. def end_of_history(self, e): # (M->)
  124. """Move to the end of the input history, i.e., the line currently
  125. being entered."""
  126. self._history.end_of_history(self.l_buffer)
  127. def _i_search(self, searchfun, direction, init_event):
  128. c = self.console
  129. line = self.get_line_buffer()
  130. query = ""
  131. hc_start = self._history.history_cursor # + direction
  132. while True:
  133. x, y = self.prompt_end_pos
  134. c.pos(0, y)
  135. if direction < 0:
  136. prompt = "reverse-i-search"
  137. else:
  138. prompt = "forward-i-search"
  139. scroll = c.write_scrolling("%s`%s': %s" % (prompt, query, line))
  140. self._update_prompt_pos(scroll)
  141. self._clear_after()
  142. event = c.getkeypress()
  143. if event.keysym == "BackSpace":
  144. if len(query) > 0:
  145. query = query[:-1]
  146. self._history.history_cursor = hc_start
  147. else:
  148. self._bell()
  149. elif (
  150. event.char in string.letters + string.digits + string.punctuation + " "
  151. ):
  152. self._history.history_cursor = hc_start
  153. query += event.char
  154. elif event.keyinfo == init_event.keyinfo:
  155. self._history.history_cursor += direction
  156. line = searchfun(query)
  157. pass
  158. else:
  159. if event.keysym != "Return":
  160. self._bell()
  161. break
  162. line = searchfun(query)
  163. px, py = self.prompt_begin_pos
  164. c.pos(0, py)
  165. self.l_buffer.set_line(line)
  166. self._print_prompt()
  167. self._history.history_cursor = len(self._history.history)
  168. def reverse_search_history(self, e): # (C-r)
  169. """Search backward starting at the current line and moving up
  170. through the history as necessary. This is an incremental search."""
  171. # print("HEJ")
  172. # self.console.bell()
  173. self._i_search(self._history.reverse_search_history, -1, e)
  174. def forward_search_history(self, e): # (C-s)
  175. """Search forward starting at the current line and moving down
  176. through the the history as necessary. This is an incremental search."""
  177. # print("HEJ")
  178. # self.console.bell()
  179. self._i_search(self._history.forward_search_history, 1, e)
  180. def non_incremental_reverse_search_history(self, e): # (M-p)
  181. """Search backward starting at the current line and moving up
  182. through the history as necessary using a non-incremental search for
  183. a string supplied by the user."""
  184. self._history.non_incremental_reverse_search_history(self.l_buffer)
  185. def non_incremental_forward_search_history(self, e): # (M-n)
  186. """Search forward starting at the current line and moving down
  187. through the the history as necessary using a non-incremental search
  188. for a string supplied by the user."""
  189. self._history.non_incremental_reverse_search_history(self.l_buffer)
  190. def history_search_forward(self, e): # ()
  191. """Search forward through the history for the string of characters
  192. between the start of the current line and the point. This is a
  193. non-incremental search. By default, this command is unbound."""
  194. self.l_buffer = self._history.history_search_forward(self.l_buffer)
  195. def history_search_backward(self, e): # ()
  196. """Search backward through the history for the string of characters
  197. between the start of the current line and the point. This is a
  198. non-incremental search. By default, this command is unbound."""
  199. self.l_buffer = self._history.history_search_backward(self.l_buffer)
  200. def yank_nth_arg(self, e): # (M-C-y)
  201. """Insert the first argument to the previous command (usually the
  202. second word on the previous line) at point. With an argument n,
  203. insert the nth word from the previous command (the words in the
  204. previous command begin with word 0). A negative argument inserts the
  205. nth word from the end of the previous command."""
  206. pass
  207. def yank_last_arg(self, e): # (M-. or M-_)
  208. """Insert last argument to the previous command (the last word of
  209. the previous history entry). With an argument, behave exactly like
  210. yank-nth-arg. Successive calls to yank-last-arg move back through
  211. the history list, inserting the last argument of each line in turn."""
  212. pass
  213. def delete_char(self, e): # (C-d)
  214. """Delete the character at point. If point is at the beginning of
  215. the line, there are no characters in the line, and the last
  216. character typed was not bound to delete-char, then return EOF."""
  217. self.l_buffer.delete_char()
  218. def backward_delete_char(self, e): # (Rubout)
  219. """Delete the character behind the cursor. A numeric argument means
  220. to kill the characters instead of deleting them."""
  221. self.l_buffer.backward_delete_char()
  222. def forward_backward_delete_char(self, e): # ()
  223. """Delete the character under the cursor, unless the cursor is at
  224. the end of the line, in which case the character behind the cursor
  225. is deleted. By default, this is not bound to a key."""
  226. pass
  227. def quoted_insert(self, e): # (C-q or C-v)
  228. """Add the next character typed to the line verbatim. This is how to
  229. insert key sequences like C-q, for example."""
  230. e = self.console.getkeypress()
  231. self.insert_text(e.char)
  232. def tab_insert(self, e): # (M-TAB)
  233. """Insert a tab character."""
  234. cursor = min(self.l_buffer.point, len(self.l_buffer.line_buffer))
  235. ws = " " * (self.tabstop - (cursor % self.tabstop))
  236. self.insert_text(ws)
  237. def self_insert(self, e): # (a, b, A, 1, !, ...)
  238. """Insert yourself."""
  239. if (
  240. ord(e.char) != 0
  241. ): # don't insert null character in buffer, can happen with dead keys.
  242. self.insert_text(e.char)
  243. def transpose_chars(self, e): # (C-t)
  244. """Drag the character before the cursor forward over the character
  245. at the cursor, moving the cursor forward as well. If the insertion
  246. point is at the end of the line, then this transposes the last two
  247. characters of the line. Negative arguments have no effect."""
  248. self.l_buffer.transpose_chars()
  249. def transpose_words(self, e): # (M-t)
  250. """Drag the word before point past the word after point, moving
  251. point past that word as well. If the insertion point is at the end
  252. of the line, this transposes the last two words on the line."""
  253. self.l_buffer.transpose_words()
  254. def upcase_word(self, e): # (M-u)
  255. """Uppercase the current (or following) word. With a negative
  256. argument, uppercase the previous word, but do not move the cursor."""
  257. self.l_buffer.upcase_word()
  258. def downcase_word(self, e): # (M-l)
  259. """Lowercase the current (or following) word. With a negative
  260. argument, lowercase the previous word, but do not move the cursor."""
  261. self.l_buffer.downcase_word()
  262. def capitalize_word(self, e): # (M-c)
  263. """Capitalize the current (or following) word. With a negative
  264. argument, capitalize the previous word, but do not move the cursor."""
  265. self.l_buffer.capitalize_word()
  266. def overwrite_mode(self, e): # ()
  267. """Toggle overwrite mode. With an explicit positive numeric
  268. argument, switches to overwrite mode. With an explicit non-positive
  269. numeric argument, switches to insert mode. This command affects only
  270. emacs mode; vi mode does overwrite differently. Each call to
  271. readline() starts in insert mode. In overwrite mode, characters
  272. bound to self-insert replace the text at point rather than pushing
  273. the text to the right. Characters bound to backward-delete-char
  274. replace the character before point with a space."""
  275. pass
  276. def kill_line(self, e): # (C-k)
  277. """Kill the text from point to the end of the line."""
  278. self.l_buffer.kill_line()
  279. def backward_kill_line(self, e): # (C-x Rubout)
  280. """Kill backward to the beginning of the line."""
  281. self.l_buffer.backward_kill_line()
  282. def unix_line_discard(self, e): # (C-u)
  283. """Kill backward from the cursor to the beginning of the current line."""
  284. # how is this different from backward_kill_line?
  285. self.l_buffer.unix_line_discard()
  286. def kill_whole_line(self, e): # ()
  287. """Kill all characters on the current line, no matter where point
  288. is. By default, this is unbound."""
  289. self.l_buffer.kill_whole_line()
  290. def kill_word(self, e): # (M-d)
  291. """Kill from point to the end of the current word, or if between
  292. words, to the end of the next word. Word boundaries are the same as
  293. forward-word."""
  294. self.l_buffer.kill_word()
  295. def backward_kill_word(self, e): # (M-DEL)
  296. """Kill the word behind point. Word boundaries are the same as
  297. backward-word."""
  298. self.l_buffer.backward_kill_word()
  299. def unix_word_rubout(self, e): # (C-w)
  300. """Kill the word behind point, using white space as a word
  301. boundary. The killed text is saved on the kill-ring."""
  302. self.l_buffer.unix_word_rubout()
  303. def delete_horizontal_space(self, e): # ()
  304. """Delete all spaces and tabs around point. By default, this is unbound."""
  305. pass
  306. def kill_region(self, e): # ()
  307. """Kill the text in the current region. By default, this command is unbound."""
  308. pass
  309. def copy_region_as_kill(self, e): # ()
  310. """Copy the text in the region to the kill buffer, so it can be
  311. yanked right away. By default, this command is unbound."""
  312. pass
  313. def copy_region_to_clipboard(self, e): # ()
  314. """Copy the text in the region to the windows clipboard."""
  315. if self.enable_win32_clipboard:
  316. mark = min(self.l_buffer.mark, len(self.l_buffer.line_buffer))
  317. cursor = min(self.l_buffer.point, len(self.l_buffer.line_buffer))
  318. if self.l_buffer.mark == -1:
  319. return
  320. begin = min(cursor, mark)
  321. end = max(cursor, mark)
  322. toclipboard = "".join(self.l_buffer.line_buffer[begin:end])
  323. clipboard.set_clipboard_text(str(toclipboard))
  324. def copy_backward_word(self, e): # ()
  325. """Copy the word before point to the kill buffer. The word
  326. boundaries are the same as backward-word. By default, this command
  327. is unbound."""
  328. pass
  329. def copy_forward_word(self, e): # ()
  330. """Copy the word following point to the kill buffer. The word
  331. boundaries are the same as forward-word. By default, this command is
  332. unbound."""
  333. pass
  334. def paste(self, e):
  335. """Paste windows clipboard"""
  336. if self.enable_win32_clipboard:
  337. txt = clipboard.get_clipboard_text_and_convert(False)
  338. self.insert_text(txt)
  339. def paste_mulitline_code(self, e):
  340. """Paste windows clipboard"""
  341. reg = re.compile("\r?\n")
  342. if self.enable_win32_clipboard:
  343. txt = clipboard.get_clipboard_text_and_convert(False)
  344. t = reg.split(txt)
  345. t = [row for row in t if row.strip() != ""] # remove empty lines
  346. if t != [""]:
  347. self.insert_text(t[0])
  348. self.add_history(self.l_buffer.copy())
  349. self.paste_line_buffer = t[1:]
  350. log("multi: %s" % self.paste_line_buffer)
  351. return True
  352. else:
  353. return False
  354. def ipython_paste(self, e):
  355. """Paste windows clipboard. If enable_ipython_paste_list_of_lists is
  356. True then try to convert tabseparated data to repr of list of lists or
  357. repr of array"""
  358. if self.enable_win32_clipboard:
  359. txt = clipboard.get_clipboard_text_and_convert(
  360. self.enable_ipython_paste_list_of_lists
  361. )
  362. if self.enable_ipython_paste_for_paths:
  363. if len(txt) < 300 and ("\t" not in txt) and ("\n" not in txt):
  364. txt = txt.replace("\\", "/").replace(" ", r"\ ")
  365. self.insert_text(txt)
  366. def yank(self, e): # (C-y)
  367. """Yank the top of the kill ring into the buffer at point."""
  368. pass
  369. def yank_pop(self, e): # (M-y)
  370. """Rotate the kill-ring, and yank the new top. You can only do this
  371. if the prior command is yank or yank-pop."""
  372. pass
  373. def digit_argument(self, e): # (M-0, M-1, ... M--)
  374. """Add this digit to the argument already accumulating, or start a
  375. new argument. M-- starts a negative argument."""
  376. pass
  377. def universal_argument(self, e): # ()
  378. """This is another way to specify an argument. If this command is
  379. followed by one or more digits, optionally with a leading minus
  380. sign, those digits define the argument. If the command is followed
  381. by digits, executing universal-argument again ends the numeric
  382. argument, but is otherwise ignored. As a special case, if this
  383. command is immediately followed by a character that is neither a
  384. digit or minus sign, the argument count for the next command is
  385. multiplied by four. The argument count is initially one, so
  386. executing this function the first time makes the argument count
  387. four, a second time makes the argument count sixteen, and so on. By
  388. default, this is not bound to a key."""
  389. pass
  390. def delete_char_or_list(self, e): # ()
  391. """Deletes the character under the cursor if not at the beginning or
  392. end of the line (like delete-char). If at the end of the line,
  393. behaves identically to possible-completions. This command is unbound
  394. by default."""
  395. pass
  396. def start_kbd_macro(self, e): # (C-x ()
  397. """Begin saving the characters typed into the current keyboard macro."""
  398. pass
  399. def end_kbd_macro(self, e): # (C-x ))
  400. """Stop saving the characters typed into the current keyboard macro
  401. and save the definition."""
  402. pass
  403. def call_last_kbd_macro(self, e): # (C-x e)
  404. """Re-execute the last keyboard macro defined, by making the
  405. characters in the macro appear as if typed at the keyboard."""
  406. pass
  407. def re_read_init_file(self, e): # (C-x C-r)
  408. """Read in the contents of the inputrc file, and incorporate any
  409. bindings or variable assignments found there."""
  410. pass
  411. def abort(self, e): # (C-g)
  412. """Abort the current editing command and ring the terminals bell
  413. (subject to the setting of bell-style)."""
  414. self._bell()
  415. def do_uppercase_version(self, e): # (M-a, M-b, M-x, ...)
  416. """If the metafied character x is lowercase, run the command that is
  417. bound to the corresponding uppercase character."""
  418. pass
  419. def prefix_meta(self, e): # (ESC)
  420. """Metafy the next character typed. This is for keyboards without a
  421. meta key. Typing ESC f is equivalent to typing M-f."""
  422. self.next_meta = True
  423. def undo(self, e): # (C-_ or C-x C-u)
  424. """Incremental undo, separately remembered for each line."""
  425. self.l_buffer.pop_undo()
  426. def revert_line(self, e): # (M-r)
  427. """Undo all changes made to this line. This is like executing the
  428. undo command enough times to get back to the beginning."""
  429. pass
  430. def tilde_expand(self, e): # (M-~)
  431. """Perform tilde expansion on the current word."""
  432. pass
  433. def set_mark(self, e): # (C-@)
  434. """Set the mark to the point. If a numeric argument is supplied, the
  435. mark is set to that position."""
  436. self.l_buffer.set_mark()
  437. def exchange_point_and_mark(self, e): # (C-x C-x)
  438. """Swap the point with the mark. The current cursor position is set
  439. to the saved position, and the old cursor position is saved as the
  440. mark."""
  441. pass
  442. def character_search(self, e): # (C-])
  443. """A character is read and point is moved to the next occurrence of
  444. that character. A negative count searches for previous occurrences."""
  445. pass
  446. def character_search_backward(self, e): # (M-C-])
  447. """A character is read and point is moved to the previous occurrence
  448. of that character. A negative count searches for subsequent
  449. occurrences."""
  450. pass
  451. def insert_comment(self, e): # (M-#)
  452. """Without a numeric argument, the value of the comment-begin
  453. variable is inserted at the beginning of the current line. If a
  454. numeric argument is supplied, this command acts as a toggle: if the
  455. characters at the beginning of the line do not match the value of
  456. comment-begin, the value is inserted, otherwise the characters in
  457. comment-begin are deleted from the beginning of the line. In either
  458. case, the line is accepted as if a newline had been typed."""
  459. pass
  460. def dump_functions(self, e): # ()
  461. """Print all of the functions and their key bindings to the Readline
  462. output stream. If a numeric argument is supplied, the output is
  463. formatted in such a way that it can be made part of an inputrc
  464. file. This command is unbound by default."""
  465. pass
  466. def dump_variables(self, e): # ()
  467. """Print all of the settable variables and their values to the
  468. Readline output stream. If a numeric argument is supplied, the
  469. output is formatted in such a way that it can be made part of an
  470. inputrc file. This command is unbound by default."""
  471. pass
  472. def dump_macros(self, e): # ()
  473. """Print all of the Readline key sequences bound to macros and the
  474. strings they output. If a numeric argument is supplied, the output
  475. is formatted in such a way that it can be made part of an inputrc
  476. file. This command is unbound by default."""
  477. pass
  478. # Create key bindings:
  479. def init_editing_mode(self, e): # (C-e)
  480. """When in vi command mode, this causes a switch to emacs editing
  481. mode."""
  482. self._bind_exit_key("Control-d")
  483. self._bind_exit_key("Control-z")
  484. # I often accidentally hold the shift or control while typing space
  485. self._bind_key("Shift-space", self.self_insert)
  486. self._bind_key("Control-space", self.self_insert)
  487. self._bind_key("Return", self.accept_line)
  488. self._bind_key("Left", self.backward_char)
  489. self._bind_key("Control-b", self.backward_char)
  490. self._bind_key("Right", self.forward_char)
  491. self._bind_key("Control-f", self.forward_char)
  492. self._bind_key("BackSpace", self.backward_delete_char)
  493. self._bind_key("Home", self.beginning_of_line)
  494. self._bind_key("End", self.end_of_line)
  495. self._bind_key("Delete", self.delete_char)
  496. self._bind_key("Control-d", self.delete_char)
  497. self._bind_key("Clear", self.clear_screen)
  498. # make it case insensitive
  499. def commonprefix(m):
  500. "Given a list of pathnames, returns the longest common leading component"
  501. if not m:
  502. return ""
  503. prefix = m[0]
  504. for item in m:
  505. for i in range(len(prefix)):
  506. if prefix[: i + 1].lower() != item[: i + 1].lower():
  507. prefix = prefix[:i]
  508. if i == 0:
  509. return ""
  510. break
  511. return prefix