I maintain two TUI libraries which use this technique and emoji support has been (nearly) great. (One of which uses your uniseg library!)
https://mitchellh.com/writing/grapheme-clusters-in-terminals
Second, and this is mostly because my personal use cases are very humble, a much, much simple to implement workaround, for everyone involved, would be a couple of OSC sequences which would mark a part of output text as the prompt (when terminal is in canonical/cooked mode), so that a huge chunk of readline could be simply thrown away.
So your program could just print a prompt, and then simply read the cooked line. In the meanwhile, the terminal emulator would handle line editing, line-wrapping and asynchronous output: if you keep outputing text to the terminal while a prompt is active, the terminal would clear the prompt and the unfinished line, print the text, then re-display the prompt and the line; basically what all "async readline" libraries do already with rl_clear/rl_redisplay — but doing it in the terminal would take care of this properly, because the terminal definitely knows how wide all the symbols it itself thinks are. And the tab completion could be supported by returning a <TAB>-terminated line to the program, instead of an <LF>-terminated line.
Unfortunately, I don't think something like this can actually become even moderately widely adopted.
Edit: Or, you know, maybe we could extend terminfo? Like, introduce twcswidth() function that would take your string, and the somehow encoded Unicode grapheme clustering data that the current terminal is actually using which you can query from terminfo, and return the number of screen cells it would take on this terminal.
I just want to, e.g. write a simple Python program that has
for line in streaming_response.lines():
print(line)
in one thread, and while True:
cmd = input('> ').strip()
if cmd == 'q':
break
if cmd == 'stop':
requests.post(...)
...
in another, and be able to input my commands without the echo of my input being teared up by the output. Erlang's shell can do that. Readline can be used to do that, but Python's bindings don't export the needed functions. Swapping out the sys.stdout/sys.stdin with my custom interceptors to do this manually... barely works, slow, ugly as hell and complicated.[0] https://github.com/thejoshwolfe/consoline
[1] https://github.com/erlang/otp/blob/90a48ae2bff26d5df67ceaa7e...