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

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