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

281 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:
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 return 0
168
169 except ValueError as e:
170 # temp var to work around mycpp runtime limitation
171 msg2 = e.message # type: str
172 self.errfmt.Print_("bind error: %s" % msg2, loc.Missing)
173 return 1
174
175 return 0
176
177
178class History(vm._Builtin):
179 """Show interactive command history."""
180
181 def __init__(
182 self,
183 readline, # type: Optional[Readline]
184 sh_files, # type: sh_init.ShellFiles
185 errfmt, # type: ui.ErrorFormatter
186 f, # type: mylib.Writer
187 ):
188 # type: (...) -> None
189 self.readline = readline
190 self.sh_files = sh_files
191 self.errfmt = errfmt
192 self.f = f # this hook is for unit testing only
193
194 def Run(self, cmd_val):
195 # type: (cmd_value.Argv) -> int
196 # NOTE: This builtin doesn't do anything in non-interactive mode in bash?
197 # It silently exits zero.
198 # zsh -c 'history' produces an error.
199 readline = self.readline
200 if not readline:
201 e_usage("is disabled because Oils wasn't compiled with 'readline'",
202 loc.Missing)
203
204 attrs, arg_r = flag_util.ParseCmdVal('history', cmd_val)
205 arg = arg_types.history(attrs.attrs)
206
207 # Clear all history
208 if arg.c:
209 readline.clear_history()
210 return 0
211
212 if arg.a:
213 hist_file = self.sh_files.HistoryFile()
214 if hist_file is None:
215 return 1
216
217 try:
218 readline.write_history_file(hist_file)
219 except (IOError, OSError) as e:
220 self.errfmt.Print_(
221 'Error writing HISTFILE %r: %s' %
222 (hist_file, pyutil.strerror(e)), loc.Missing)
223 return 1
224
225 return 0
226
227 if arg.r:
228 hist_file = self.sh_files.HistoryFile()
229 if hist_file is None:
230 return 1
231
232 try:
233 readline.read_history_file(hist_file)
234 except (IOError, OSError) as e:
235 self.errfmt.Print_(
236 'Error reading HISTFILE %r: %s' %
237 (hist_file, pyutil.strerror(e)), loc.Missing)
238 return 1
239
240 return 0
241
242 # Delete history entry by id number
243 arg_d = mops.BigTruncate(arg.d)
244 if arg_d >= 0:
245 cmd_index = arg_d - 1
246
247 try:
248 readline.remove_history_item(cmd_index)
249 except ValueError:
250 e_usage("couldn't find item %d" % arg_d, loc.Missing)
251
252 return 0
253
254 # Returns 0 items in non-interactive mode?
255 num_items = readline.get_current_history_length()
256 #log('len = %d', num_items)
257
258 num_arg, num_arg_loc = arg_r.Peek2()
259
260 if num_arg is None:
261 start_index = 1
262 else:
263 try:
264 num_to_show = int(num_arg)
265 except ValueError:
266 e_usage('got invalid argument %r' % num_arg, num_arg_loc)
267 start_index = max(1, num_items + 1 - num_to_show)
268
269 arg_r.Next()
270 if not arg_r.AtEnd():
271 e_usage('got too many arguments', loc.Missing)
272
273 # TODO:
274 # - Exclude lines that don't parse from the history! bash and zsh don't do
275 # that.
276 # - Consolidate multiline commands.
277
278 for i in xrange(start_index, num_items + 1): # 1-based index
279 item = readline.get_history_item(i)
280 self.f.write('%5d %s\n' % (i, item))
281 return 0