OILS / builtin / readline_osh.py View on Github | oilshell.org

268 lines, 165 significant
1#!/usr/bin/env python2
2"""
3readline_osh.py - Builtins that are dependent on GNU readline.
4"""
5from __future__ import print_function
6
7from _devbuild.gen import arg_types
8from _devbuild.gen.syntax_asdl import loc
9from _devbuild.gen.value_asdl import value, value_e
10from core import pyutil
11from core import vm
12from core.error import e_usage
13from frontend import flag_util
14from mycpp import mops
15from mycpp import mylib
16
17from typing import Optional, Any, TYPE_CHECKING
18if TYPE_CHECKING:
19 from _devbuild.gen.runtime_asdl import cmd_value
20 from frontend.py_readline import Readline
21 from core import sh_init
22 from display import ui
23
24class ctx_Keymap(object):
25
26 def __init__(self, readline, keymap_name = None):
27 # type: (Readline, str) -> None
28 self.readline = readline
29 self.orig_keymap_name = keymap_name
30
31 def __enter__(self):
32 # type: () -> None
33 if self.orig_keymap_name:
34 self.readline.use_temp_keymap(self.orig_keymap_name)
35
36 def __exit__(self, type, value, traceback):
37 # type: (Any, Any, Any) -> None
38 if self.orig_keymap_name is not None:
39 self.readline.restore_orig_keymap()
40
41
42class Bind(vm._Builtin):
43 """Interactive interface to readline bindings"""
44
45 def __init__(self, readline, errfmt):
46 # type: (Optional[Readline], ui.ErrorFormatter) -> None
47 self.readline = readline
48 self.errfmt = errfmt
49 self.exclusive_flags = ["q", "u", "r", "x", "f"]
50
51 def Run(self, cmd_val):
52 # type: (cmd_value.Argv) -> int
53 readline = self.readline
54 if not readline:
55 e_usage("is disabled because Oils wasn't compiled with 'readline'",
56 loc.Missing)
57
58 attrs, arg_r = flag_util.ParseCmdVal('bind', cmd_val)
59
60 # print("attrs:\n", attrs)
61 # print("attrs.attrs:\n", attrs.attrs)
62 # print("attrs.attrs.m:\n", attrs.attrs["m"])
63 # print("type(attrs.attrs.m):\n", type(attrs.attrs["m"]))
64 # print("type(attrs.attrs[m]):\n", type(attrs.attrs["m"]))
65 # print("attrs.attrs[m].tag() :\n", attrs.attrs["m"].tag())
66 # print("attrs.attrs[m].tag() == value_e.Undef:\n", attrs.attrs["m"].tag() == value_e.Undef)
67 # print(arg_r)
68 # print("Reader argv=%s locs=%s n=%i i=%i" % (arg_r.argv, str(arg_r.locs), arg_r.n, arg_r.i))
69
70 # Check mutually-exclusive flags and non-flag args
71 found = False
72 for flag in self.exclusive_flags:
73 if flag in attrs.attrs and attrs.attrs[flag].tag() != value_e.Undef:
74 # print("\tFound flag: {0} with tag: {1}".format(flag, attrs.attrs[flag].tag()))
75 if found:
76 self.errfmt.Print_("error: can only use one of the following flags at a time: -" + ", -".join(self.exclusive_flags), blame_loc=cmd_val.arg_locs[0])
77 return 1
78 else:
79 found = True
80 if found and not arg_r.AtEnd():
81 self.errfmt.Print_("error: cannot mix bind commands with the following flags: -" + ", -".join(self.exclusive_flags), blame_loc=cmd_val.arg_locs[0])
82 return 1
83
84
85 arg = arg_types.bind(attrs.attrs)
86 # print("arg:\n", arg)
87 # print("dir(arg):\n", dir(arg))
88 # for prop in dir(arg):
89 # if not prop.startswith('__'):
90 # value = getattr(arg, prop)
91 # print("Property: {0}, Value: {1}".format(prop, value))
92 # print("arg.m:\n", arg.m)
93
94
95 try:
96 with ctx_Keymap(readline, arg.m): # Replicates bind's -m behavior
97
98 # This gauntlet of ifs is meant to replicate bash behavior, in case we
99 # need to relax the mutual exclusion of flags like bash does
100
101 if arg.l:
102 readline.list_funmap_names()
103
104 if arg.p:
105 readline.function_dumper(True)
106
107 if arg.P:
108 readline.function_dumper(False)
109
110 if arg.s:
111 readline.macro_dumper(True)
112
113 if arg.S:
114 readline.macro_dumper(False)
115
116 if arg.v:
117 readline.variable_dumper(True)
118
119 if arg.V:
120 readline.variable_dumper(False)
121
122 if arg.f is not None:
123 readline.read_init_file(arg.f)
124
125 if arg.q is not None:
126 readline.query_bindings(arg.q)
127
128 if arg.u is not None:
129 readline.unbind_rl_function(arg.u)
130
131 if 0:
132 if arg.r is not None:
133 readline.unbind_keyseq(arg.r)
134
135 if arg.x is not None:
136 self.errfmt.Print_("warning: bind -x isn't implemented",
137 blame_loc=cmd_val.arg_locs[0])
138 return 1
139
140 if arg.X is not None:
141 readline.print_shell_cmd_map()
142
143 bindings, arg_locs = arg_r.Rest2()
144 for i, binding in enumerate(bindings):
145 try:
146 # print("Binding %s (%i)", binding, i)
147 # print("Arg loc %s (%i)", arg_locs[i], i)
148 readline.parse_and_bind(binding)
149 except ValueError as e:
150 msg = e.message # type: str
151 self.errfmt.Print_("bind error: %s" % msg, arg_locs[i])
152 return 1
153
154 return 0
155
156 except ValueError as e:
157 # temp var to work around mycpp runtime limitation
158 msg2 = e.message # type: str
159 self.errfmt.Print_("bind error: %s" % msg2, loc.Missing)
160 return 1
161
162 return 0
163
164
165class History(vm._Builtin):
166 """Show interactive command history."""
167
168 def __init__(
169 self,
170 readline, # type: Optional[Readline]
171 sh_files, # type: sh_init.ShellFiles
172 errfmt, # type: ui.ErrorFormatter
173 f, # type: mylib.Writer
174 ):
175 # type: (...) -> None
176 self.readline = readline
177 self.sh_files = sh_files
178 self.errfmt = errfmt
179 self.f = f # this hook is for unit testing only
180
181 def Run(self, cmd_val):
182 # type: (cmd_value.Argv) -> int
183 # NOTE: This builtin doesn't do anything in non-interactive mode in bash?
184 # It silently exits zero.
185 # zsh -c 'history' produces an error.
186 readline = self.readline
187 if not readline:
188 e_usage("is disabled because Oils wasn't compiled with 'readline'",
189 loc.Missing)
190
191 attrs, arg_r = flag_util.ParseCmdVal('history', cmd_val)
192 arg = arg_types.history(attrs.attrs)
193
194 # Clear all history
195 if arg.c:
196 readline.clear_history()
197 return 0
198
199 if arg.a:
200 hist_file = self.sh_files.HistoryFile()
201 if hist_file is None:
202 return 1
203
204 try:
205 readline.write_history_file(hist_file)
206 except (IOError, OSError) as e:
207 self.errfmt.Print_(
208 'Error writing HISTFILE %r: %s' %
209 (hist_file, pyutil.strerror(e)), loc.Missing)
210 return 1
211
212 return 0
213
214 if arg.r:
215 hist_file = self.sh_files.HistoryFile()
216 if hist_file is None:
217 return 1
218
219 try:
220 readline.read_history_file(hist_file)
221 except (IOError, OSError) as e:
222 self.errfmt.Print_(
223 'Error reading HISTFILE %r: %s' %
224 (hist_file, pyutil.strerror(e)), loc.Missing)
225 return 1
226
227 return 0
228
229 # Delete history entry by id number
230 arg_d = mops.BigTruncate(arg.d)
231 if arg_d >= 0:
232 cmd_index = arg_d - 1
233
234 try:
235 readline.remove_history_item(cmd_index)
236 except ValueError:
237 e_usage("couldn't find item %d" % arg_d, loc.Missing)
238
239 return 0
240
241 # Returns 0 items in non-interactive mode?
242 num_items = readline.get_current_history_length()
243 #log('len = %d', num_items)
244
245 num_arg, num_arg_loc = arg_r.Peek2()
246
247 if num_arg is None:
248 start_index = 1
249 else:
250 try:
251 num_to_show = int(num_arg)
252 except ValueError:
253 e_usage('got invalid argument %r' % num_arg, num_arg_loc)
254 start_index = max(1, num_items + 1 - num_to_show)
255
256 arg_r.Next()
257 if not arg_r.AtEnd():
258 e_usage('got too many arguments', loc.Missing)
259
260 # TODO:
261 # - Exclude lines that don't parse from the history! bash and zsh don't do
262 # that.
263 # - Consolidate multiline commands.
264
265 for i in xrange(start_index, num_items + 1): # 1-based index
266 item = readline.get_history_item(i)
267 self.f.write('%5d %s\n' % (i, item))
268 return 0