Skip to content

Python bind for osh #2092

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 41 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
cb46bb1
Initial bind support; -l option added
KingMob Oct 6, 2024
ece9fc2
Initial native support for bind; only -l
KingMob Oct 6, 2024
a59647d
Python support for informational bind options
KingMob Oct 6, 2024
acca050
Check for mutually-exclusive flags/commands in bind
KingMob Oct 20, 2024
e269bdb
Add support and test for bind -f inputrc files
KingMob Oct 20, 2024
21539bd
[builtin] Add support for bind -q
KingMob Oct 27, 2024
28444cc
[builtin] Add bind -u support
KingMob Nov 3, 2024
6f9fd17
[builtin] Add bind -m support for keymaps
KingMob Nov 3, 2024
2ab2882
[refactor] Rename unbind_command to unbind_rl_function for clarity
KingMob Nov 3, 2024
20b1ef2
[builtin] Add initial bind support for shell command keymaps
KingMob Nov 3, 2024
c26b7d5
[builtin] Add bind -r support for keyseq unbinding
KingMob Nov 3, 2024
0f09f4e
[types] Make it pass devtools/types.sh check-oils
Nov 5, 2024
50476fb
[translation] Fix mycpp errors
Nov 5, 2024
3a275a6
Update pyreadline.h header
Nov 6, 2024
296a621
Fix C++ compile errors
Nov 6, 2024
da3f1e0
Align bind -l with bash version that Python's readline.c is based off of
KingMob Nov 17, 2024
b740f85
Add basic bind support for non-unix commands, i.e., readline fns
KingMob Nov 17, 2024
d47c283
Merge branch 'master' into kingmob/push-mzoxrymlqqwp
Nov 18, 2024
5af55d0
Try disabling function that uses rl_function_of_keyseq_len
Nov 18, 2024
4ef456b
fix C++ compile error
Nov 18, 2024
657ec55
Fix lint error
Nov 18, 2024
ed7f407
[spec/builtin-bind] Relax spec test
Nov 18, 2024
c7c8965
[spec/stateful/bind] Allow 2 failures
Nov 18, 2024
23f17c8
[reformat] Run yapf
Nov 18, 2024
d86394d
format
Nov 18, 2024
7e97e67
[builtin/bind] Add comments
Nov 18, 2024
85b51e6
Merge branch 'master' into kingmob/push-mzoxrymlqqwp
Nov 18, 2024
7b94dc2
disable assert(0) - does it fix ble.sh?
Nov 18, 2024
2212e55
merge
Nov 18, 2024
06dc329
[test/runtime-errors] Disable tests for now
Nov 18, 2024
ba97e9a
test against all
Nov 18, 2024
ca93122
faster testing
Nov 18, 2024
33ba587
line_input: Fix C warnings
Nov 18, 2024
fbf3470
more tests
Nov 18, 2024
ab3d1b2
[spec/builtin-bind] Add test cases
Nov 18, 2024
2e46920
[pyext/line_input.c] Comments
Nov 18, 2024
d1dcdf7
[test/spec] Adjust allowed failures
Nov 18, 2024
d2fa81a
[spec/builtin-bind] Adjust test cases
Nov 18, 2024
4efbbaf
Comment, and remove testdata
Nov 18, 2024
05b8f3f
Fix bind -q missing fn output
KingMob Nov 19, 2024
e101fb1
Add missing explicit checks for None in bind code
KingMob Nov 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 147 additions & 7 deletions builtin/readline_osh.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,179 @@
#!/usr/bin/env python2
"""
builtin_lib.py - Builtins that are bindings to libraries, e.g. GNU readline.
readline_osh.py - Builtins that are dependent on GNU readline.
"""
from __future__ import print_function

from _devbuild.gen import arg_types
from _devbuild.gen.syntax_asdl import loc
from core.error import e_usage
from _devbuild.gen.value_asdl import value_e
from core import pyutil
from core import vm
from core.error import e_usage
from frontend import flag_util
from mycpp import mops
from mycpp import mylib
from mycpp.mylib import log

from typing import Optional, TYPE_CHECKING
from typing import Optional, Any, TYPE_CHECKING
if TYPE_CHECKING:
from _devbuild.gen.runtime_asdl import cmd_value
from frontend.py_readline import Readline
from core import sh_init
from display import ui

_ = log


class ctx_Keymap(object):

def __init__(self, readline, keymap_name=None):
# type: (Readline, str) -> None
self.readline = readline
self.orig_keymap_name = keymap_name

def __enter__(self):
# type: () -> None
if self.orig_keymap_name is not None:
self.readline.use_temp_keymap(self.orig_keymap_name)

def __exit__(self, type, value, traceback):
# type: (Any, Any, Any) -> None
if self.orig_keymap_name is not None:
self.readline.restore_orig_keymap()


class Bind(vm._Builtin):
"""For :, true, false."""
"""Interactive interface to readline bindings"""

def __init__(self, readline, errfmt):
# type: (Optional[Readline], ui.ErrorFormatter) -> None
self.readline = readline
self.errfmt = errfmt
self.exclusive_flags = ["q", "u", "r", "x", "f"]

def Run(self, cmd_val):
# type: (cmd_value.Argv) -> int
self.errfmt.Print_("warning: bind isn't implemented",
blame_loc=cmd_val.arg_locs[0])
return 1
readline = self.readline
if not readline:
e_usage("is disabled because Oils wasn't compiled with 'readline'",
loc.Missing)

attrs, arg_r = flag_util.ParseCmdVal('bind', cmd_val)

# print("attrs:\n", attrs)
# print("attrs.attrs:\n", attrs.attrs)
# print("attrs.attrs.m:\n", attrs.attrs["m"])
# print("type(attrs.attrs.m):\n", type(attrs.attrs["m"]))
# print("type(attrs.attrs[m]):\n", type(attrs.attrs["m"]))
# print("attrs.attrs[m].tag() :\n", attrs.attrs["m"].tag())
# print("attrs.attrs[m].tag() == value_e.Undef:\n", attrs.attrs["m"].tag() == value_e.Undef)
# print(arg_r)
# print("Reader argv=%s locs=%s n=%i i=%i" % (arg_r.argv, str(arg_r.locs), arg_r.n, arg_r.i))

# Check mutually-exclusive flags and non-flag args
found = False
for flag in self.exclusive_flags:
if (flag in attrs.attrs and
attrs.attrs[flag].tag() != value_e.Undef):
# print("\tFound flag: {0} with tag: {1}".format(flag, attrs.attrs[flag].tag()))
if found:
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])
return 1
else:
found = True
if found and not arg_r.AtEnd():
self.errfmt.Print_(
"error: cannot mix bind commands with the following flags: -" +
", -".join(self.exclusive_flags),
blame_loc=cmd_val.arg_locs[0])
return 1

arg = arg_types.bind(attrs.attrs)
# print("arg:\n", arg)
# print("dir(arg):\n", dir(arg))
# for prop in dir(arg):
# if not prop.startswith('__'):
# value = getattr(arg, prop)
# print("Property: {0}, Value: {1}".format(prop, value))
# print("arg.m:\n", arg.m)

try:
with ctx_Keymap(readline, arg.m): # Replicates bind's -m behavior

# This gauntlet of ifs is meant to replicate bash behavior, in case we
# need to relax the mutual exclusion of flags like bash does

# List names of functions
if arg.l:
readline.list_funmap_names()

# Print function names and bindings
if arg.p:
readline.function_dumper(True) # reusable as input
if arg.P:
readline.function_dumper(False)

# Print macros
if arg.s:
readline.macro_dumper(True) # reusable as input
if arg.S:
readline.macro_dumper(False)

# Print readline variable names
if arg.v:
readline.variable_dumper(True)
if arg.V:
readline.variable_dumper(False)

if arg.f is not None:
readline.read_init_file(arg.f)

if arg.q is not None:
readline.query_bindings(arg.q)

if arg.u is not None:
readline.unbind_rl_function(arg.u)

if 0:
# disabled until we fix error with rl_function_of_keyseq_len()
if arg.r is not None:
readline.unbind_keyseq(arg.r)

if arg.x is not None:
self.errfmt.Print_("warning: bind -x isn't implemented",
blame_loc=cmd_val.arg_locs[0])
return 1

if arg.X:
readline.print_shell_cmd_map()

bindings, arg_locs = arg_r.Rest2()
#log('bindings %d locs %d', len(arg_r.argv), len(arg_r.locs))

for i, binding in enumerate(bindings):
try:
#log("Binding %s (%d)", binding, i)
#log("Arg loc %s (%d)", arg_locs[i], i)
readline.parse_and_bind(binding)
except ValueError as e:
msg = e.message # type: str
self.errfmt.Print_("bind error: %s" % msg, arg_locs[i])
return 1

except ValueError as e:
# only print out the exception message if non-empty
# some bash bind errors return non-zero, but print to stdout
# temp var to work around mycpp runtime limitation
msg2 = e.message # type: str
if msg2 is not None and len(msg2) > 0:
self.errfmt.Print_("bind error: %s" % msg2, loc.Missing)
return 1

return 0


class History(vm._Builtin):
Expand Down
49 changes: 49 additions & 0 deletions cpp/frontend_pyreadline.cc
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,55 @@ void Readline::resize_terminal() {
#endif
}

// bind fns
void Readline::list_funmap_names() {
#if HAVE_READLINE
rl_list_funmap_names();
#else
assert(0); // not implemented
#endif
}

void Readline::read_init_file(BigStr* s) {
// assert(0); // not implemented
}

void Readline::function_dumper(bool print_readably) {
// assert(0); // not implemented
}

void Readline::macro_dumper(bool print_readably) {
// assert(0); // not implemented
}

void Readline::variable_dumper(bool print_readably) {
// assert(0); // not implemented
}

void Readline::query_bindings(BigStr* fn_name) {
// assert(0); // not implemented
}

void Readline::unbind_rl_function(BigStr* fn_name) {
// assert(0); // not implemented
}

void Readline::use_temp_keymap(BigStr* fn_name) {
// assert(0); // not implemented
}

void Readline::restore_orig_keymap() {
// assert(0); // not implemented
}

void Readline::print_shell_cmd_map() {
// assert(0); // not implemented
}

void Readline::unbind_keyseq(BigStr* keyseq) {
// assert(0); // not implemented
}

Readline* MaybeGetReadline() {
#if HAVE_READLINE
gReadline = Alloc<Readline>();
Expand Down
13 changes: 13 additions & 0 deletions cpp/frontend_pyreadline.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,19 @@ class Readline {
int get_current_history_length();
void resize_terminal();

// Functions added to implement the 'bind' builtin in OSH
void list_funmap_names();
void read_init_file(BigStr* s);
void function_dumper(bool print_readably);
void macro_dumper(bool print_readably);
void variable_dumper(bool print_readably);
void query_bindings(BigStr* fn_name);
void unbind_rl_function(BigStr* fn_name);
void use_temp_keymap(BigStr* fn_name);
void restore_orig_keymap();
void print_shell_cmd_map();
void unbind_keyseq(BigStr* keyseq);

static constexpr uint32_t field_mask() {
return maskbit(offsetof(Readline, completer_delims_)) |
maskbit(offsetof(Readline, completer_)) |
Expand Down
16 changes: 16 additions & 0 deletions frontend/flag_def.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,22 @@
#HELP_SPEC.ShortFlag('-i') # show index
# Note: bash has help -d -m -s, which change the formatting

BIND_SPEC = FlagSpec('bind')
BIND_SPEC.ShortFlag('-m', args.String)
BIND_SPEC.ShortFlag('-q', args.String)
BIND_SPEC.ShortFlag('-u', args.String)
BIND_SPEC.ShortFlag('-r', args.String)
BIND_SPEC.ShortFlag('-f', args.String)
BIND_SPEC.ShortFlag('-x', args.String)
BIND_SPEC.ShortFlag('-l')
BIND_SPEC.ShortFlag('-p')
BIND_SPEC.ShortFlag('-s')
BIND_SPEC.ShortFlag('-v')
BIND_SPEC.ShortFlag('-P')
BIND_SPEC.ShortFlag('-S')
BIND_SPEC.ShortFlag('-V')
BIND_SPEC.ShortFlag('-X')

HISTORY_SPEC = FlagSpec('history')
HISTORY_SPEC.ShortFlag('-a')
HISTORY_SPEC.ShortFlag('-r')
Expand Down
44 changes: 44 additions & 0 deletions frontend/py_readline.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ def parse_and_bind(self, s):
# type: (str) -> None
line_input.parse_and_bind(s)

def read_init_file(self, s):
# type: (str) -> None
line_input.read_init_file(s)

def add_history(self, line):
# type: (str) -> None
line_input.add_history(line)
Expand Down Expand Up @@ -94,6 +98,46 @@ def resize_terminal(self):
# type: () -> None
line_input.resize_terminal()

def list_funmap_names(self):
# type: () -> None
line_input.list_funmap_names()

def function_dumper(self, print_readably):
# type: (bool) -> None
line_input.function_dumper(print_readably)

def macro_dumper(self, print_readably):
# type: (bool) -> None
line_input.macro_dumper(print_readably)

def variable_dumper(self, print_readably):
# type: (bool) -> None
line_input.variable_dumper(print_readably)

def query_bindings(self, fn_name):
# type: (str) -> None
line_input.query_bindings(fn_name)

def unbind_rl_function(self, fn_name):
# type: (str) -> None
line_input.unbind_rl_function(fn_name)

def use_temp_keymap(self, fn_name):
# type: (str) -> None
line_input.use_temp_keymap(fn_name)

def restore_orig_keymap(self):
# type: () -> None
line_input.restore_orig_keymap()

def print_shell_cmd_map(self):
# type: () -> None
line_input.print_shell_cmd_map()

def unbind_keyseq(self, keyseq):
# type: (str) -> None
line_input.unbind_keyseq(keyseq)


def MaybeGetReadline():
# type: () -> Optional[Readline]
Expand Down
Loading