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

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