I'm trying to get multiline history completion work by history -z | fzf --read0. However it generates items that are not separated by \n but \? which breaks command line substitution.

fzf does not officially support multi-line entries, and with the premise, a new line character is not a valid character fzf can properly display on screen, and fzf prints it as ? character instead. I know it is not ideal, but it's how it works right now.
fzf displays those characters as ?, but that does not affect the final output of fzf, so you can still depend on it.
> echo -en "foo\nbar" | fzf --read0 --select-1 | wc -l
2
Notice that we have no issues with multi-line commands in bash and zsh, as history and fc -l 1 commands of them are smart enough to transform a multi-line command into a single-line entry.
$ a() {
> 1
> 2
> }
$ HISTTIMEFORMAT= history | tail -2
94918 a() { 1; 2; }
94919 HISTTIMEFORMAT= history | tail -2
$
Maybe fish can do something similar?
ah, fish's read command also needs a -z flag. This works history -z | eval (__fzfcmd) --read0 -q '$str' | read -lz result
Most helpful comment
fzf does not officially support multi-line entries, and with the premise, a new line character is not a valid character fzf can properly display on screen, and fzf prints it as
?character instead. I know it is not ideal, but it's how it works right now.fzf displays those characters as
?, but that does not affect the final output of fzf, so you can still depend on it.Notice that we have no issues with multi-line commands in bash and zsh, as
historyandfc -l 1commands of them are smart enough to transform a multi-line command into a single-line entry.Maybe fish can do something similar?