fish-shell/share/functions/edit_command_buffer.fish

129 lines
4.6 KiB
Fish
Raw Normal View History

function edit_command_buffer --description 'Edit the command buffer in an external editor'
set -l f (mktemp)
or return 1
if set -q f[1]
command mv $f $f.fish
set f $f.fish
else
# We should never execute this block but better to be paranoid.
if set -q TMPDIR
2018-03-16 07:31:15 +08:00
set f $TMPDIR/fish.$fish_pid.fish
else
2018-03-16 07:31:15 +08:00
set f /tmp/fish.$fish_pid.fish
end
command touch $f
or return 1
end
Open command script in external editor on Alt+o Fish functions are great for configuring fish but they don't integrate seamlessly with the rest of the system. For tasks that can run outside fish, writing scripts is the natural approach. To edit my scripts I frequently run $EDITOR (which my-script) Would be great to reduce the amount typing for this common case (the names of editor and scripts are usually short, so that's a lot of typing spent on the boring part). Our Alt+o binding opens the file at the cursor in a pager. When the cursor is in command position, it doesn't do anything (unless the command is actually a valid file path). Let's make it open the resolved file path in an editor. In future, we should teach this binding to delegate to "funced" upon seeing a function instead of a script. I didn't do it yet because funced prints messages, so it will mess with the commandline rendering if used from a binding. (The fact that funced encourages overwriting functions that ship with fish is worrysome. Also I'm not sure why funced doesn't open the function's source file directly (if not sourced from stdin). Persisting the function should probably be the default.) Alternative approach: I think other shells expand "=my-script" to "/path/to/my-script". That is certainly an option -- if we do that we'd want to teach fish to complete command names after "=". Since I don't remember scenarios where I care about the full path of a script beyond opening it in my editor, I didn't look further into this. Closes #10266
2024-01-26 18:06:17 +08:00
set -l editor (__fish_anyeditor)
or return 1
set -l indented_lines (commandline -b | fish_indent --only-indent)
string join -- \n $indented_lines >$f
set -l offset (commandline --cursor)
# compute cursor line/column
set -l lines (commandline)\n
set -l line 1
while test $offset -ge (string length -- $lines[1])
set offset (math $offset - (string length -- $lines[1]))
set line (math $line + 1)
set -e lines[1]
end
set -l indent 1 + (string length -- $indented_lines[$line]) - (string length -- $lines[1])
set -l col (math $offset + 1 + $indent)
set -l editor_basename (string match -r '[^/]+$' -- $editor[1])
set -l wrapped_commands
for wrap_target in (complete -- $editor_basename | string replace -rf '^complete [^/]+ --wraps (.+)$' '$1')
set -l tmp
string unescape -- $wrap_target | read -at tmp
set -a wrapped_commands $tmp[1]
end
set -l found false
set -l cursor_from_editor
for editor_command in $editor_basename $wrapped_commands
switch $editor_command
case vi vim nvim
if test $editor_command = vi && not set -l vi_version "$(vi --version 2>/dev/null)"
if printf %s $vi_version | grep -q BusyBox
break
end
set -a editor +{$line} $f
set found true
break
end
set cursor_from_editor (mktemp)
set -a editor +$line "+norm! $col|" $f \
'+autocmd VimLeave * ++once call writefile(
[printf("%s %s %s", shellescape(bufname()), line("."), col("."))],
"'$cursor_from_editor'"
)'
case emacs emacsclient gedit
set -a editor +$line:$col $f
case kak
set cursor_from_editor (mktemp)
set -a editor +$line:$col $f -e "
hook -always -once global ClientClose %val{client} %{
echo -to-file $cursor_from_editor -quoting shell \
%val{buffile} %val{cursor_line} %val{cursor_column}
}
"
case nano
set -a editor +$line,$col $f
case joe ee
set -a editor +$line $f
case code code-oss
set -a editor --goto $f:$line:$col --wait
case subl
set -a editor $f:$line:$col --wait
case micro
set -a editor $f +$line:$col
case '*'
continue
end
set found true
break
end
if not $found
set -a editor $f
end
$editor
set -l raw_lines (command cat $f)
set -l unindented_lines (string join -- \n $raw_lines | fish_indent --only-unindent)
# Here we're checking the exit status of the editor.
if test $status -eq 0 -a -s $f
# Set the command to the output of the edited command and move the cursor to the
# end of the edited command.
commandline -r -- $unindented_lines
commandline -C 999999
else
echo
echo (_ "Ignoring the output of your editor since its exit status was non-zero")
echo (_ "or the file was empty")
end
if set -q cursor_from_editor[1]
eval set -l pos "$(cat $cursor_from_editor)"
if set -q pos[1] && test $pos[1] = $f
set -l line $pos[2]
set -l indent (math (string length -- $raw_lines[$line]) - (string length -- $unindented_lines[$line]))
set -l column (math $pos[3] - $indent)
commandline -C 0
for _line in (seq $line)[2..]
commandline -f down-line
end
commandline -f beginning-of-line
for _column in (seq $column)[2..]
commandline -f forward-single-char
end
end
command rm $cursor_from_editor
end
command rm $f
# We've probably opened something that messed with the screen.
# A repaint seems in order.
commandline -f repaint
end