mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-06 16:05:05 +02:00
Merge branch 'copilot/fix-issue-10198' of https://github.com/kovidgoyal/kitty
This commit is contained in:
@@ -943,6 +943,7 @@ class EditCmd:
|
||||
self.file_size = -1
|
||||
self.version = 0
|
||||
self.source_window_id = self.editor_window_id = -1
|
||||
self.editor_exit_code: int | None = None
|
||||
simple = 'file_inode', 'file_data', 'abort_signaled', 'version'
|
||||
for k, v in parse_message(msg, simple):
|
||||
if k == 'file_inode':
|
||||
@@ -1009,6 +1010,17 @@ class EditCmd:
|
||||
|
||||
def on_edit_window_close(self, window: Window) -> None:
|
||||
self.check_status()
|
||||
if self.editor_exit_code is None:
|
||||
# The PID death callback fires in the same main-loop tick as the
|
||||
# window-close callback (report_reaped_pids runs after parse_input).
|
||||
# Schedule a short-interval fallback timer in case the PID was not
|
||||
# registered in time (e.g., editor crashed before monitor_pid ran).
|
||||
add_timer(self._fallback_done, 0.1, False)
|
||||
|
||||
def _fallback_done(self, timer_id: int | None = None) -> None:
|
||||
if self.editor_exit_code is None:
|
||||
self.editor_exit_code = 0
|
||||
self.check_status()
|
||||
|
||||
def check_status(self, timer_id: int | None = None) -> None:
|
||||
if self.abort_signaled:
|
||||
@@ -1023,9 +1035,13 @@ class EditCmd:
|
||||
self.send_data(source_window, 'UPDATE', data)
|
||||
editor_window = boss.window_id_map.get(self.editor_window_id)
|
||||
if editor_window is None:
|
||||
if self.editor_exit_code is None:
|
||||
# Wait for the PID death callback to provide the editor's exit code.
|
||||
# It will call check_status() again once the exit code is known.
|
||||
return
|
||||
edits_in_flight.pop(self.source_window_id, None)
|
||||
if source_window is not None:
|
||||
self.send_data(source_window, 'DONE')
|
||||
self.send_data(source_window, 'DONE', str(self.editor_exit_code).encode())
|
||||
self.abort_signaled = self.abort_signaled or 'closed'
|
||||
else:
|
||||
self.schedule_check()
|
||||
@@ -1142,6 +1158,19 @@ def remote_edit(msg: str, window: Window) -> None:
|
||||
q.abort_signaled = 'replaced'
|
||||
edits_in_flight[window.id] = c
|
||||
w.actions_on_close.append(c.on_edit_window_close)
|
||||
editor_pid = w.child.pid
|
||||
if editor_pid:
|
||||
def on_editor_pid_death(wait_status: int, err: Exception | None) -> None:
|
||||
c.editor_exit_code = os.waitstatus_to_exitcode(wait_status) if err is None else 1
|
||||
c.check_status()
|
||||
try:
|
||||
get_boss().monitor_pid(editor_pid, on_editor_pid_death)
|
||||
except RuntimeError:
|
||||
# PID monitoring table is full. The fallback timer in
|
||||
# on_edit_window_close will set editor_exit_code to 0.
|
||||
log_error('edit-in-kitty: PID monitoring table full, editor exit code will not be tracked')
|
||||
else:
|
||||
c.editor_exit_code = 0
|
||||
c.schedule_check()
|
||||
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ func encode(x string) string {
|
||||
|
||||
type OnDataCallback = func(data_type string, data []byte) error
|
||||
|
||||
func edit_loop(data_to_send string, kill_if_signaled bool, on_data OnDataCallback) (err error) {
|
||||
func edit_loop(data_to_send string, kill_if_signaled bool, on_data OnDataCallback) (exit_code int, err error) {
|
||||
lp, err := loop.New(loop.NoAlternateScreen, loop.NoRestoreColors, loop.NoMouseTracking)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -52,7 +52,18 @@ func edit_loop(data_to_send string, kill_if_signaled bool, on_data OnDataCallbac
|
||||
if line == "KITTY_DATA_END" {
|
||||
lp.QueueWriteString(update_type + "\r\n")
|
||||
if update_type == "DONE" {
|
||||
lp.Quit(0)
|
||||
// Parse the editor exit code from the data payload.
|
||||
// Decoding errors are intentionally ignored: if the data is
|
||||
// missing or malformed (e.g. from an older kitty server),
|
||||
// we fall back to exit_code=0 for backward compatibility.
|
||||
if data.Len() > 0 {
|
||||
if b, err2 := base64.StdEncoding.DecodeString(data.String()); err2 == nil {
|
||||
if n, err3 := strconv.Atoi(strings.TrimSpace(string(b))); err3 == nil {
|
||||
exit_code = n
|
||||
}
|
||||
}
|
||||
}
|
||||
lp.Quit(exit_code)
|
||||
return nil
|
||||
}
|
||||
b, err := base64.StdEncoding.DecodeString(data.String())
|
||||
@@ -150,7 +161,7 @@ func edit_loop(data_to_send string, kill_if_signaled bool, on_data OnDataCallbac
|
||||
return
|
||||
}
|
||||
if canceled {
|
||||
return tui.Canceled
|
||||
return 1, tui.Canceled
|
||||
}
|
||||
|
||||
ds := lp.DeathSignalName()
|
||||
@@ -160,29 +171,29 @@ func edit_loop(data_to_send string, kill_if_signaled bool, on_data OnDataCallbac
|
||||
lp.KillIfSignalled()
|
||||
return
|
||||
}
|
||||
return &tui.KilledBySignal{Msg: fmt.Sprint("Killed by signal: ", ds), SignalName: ds}
|
||||
return 1, &tui.KilledBySignal{Msg: fmt.Sprint("Killed by signal: ", ds), SignalName: ds}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func edit_in_kitty(path string, opts *Options) (err error) {
|
||||
func edit_in_kitty(path string, opts *Options) (exit_code int, err error) {
|
||||
read_file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to open %s for reading with error: %w", path, err)
|
||||
return 1, fmt.Errorf("Failed to open %s for reading with error: %w", path, err)
|
||||
}
|
||||
defer read_file.Close()
|
||||
var s unix.Stat_t
|
||||
err = unix.Fstat(int(read_file.Fd()), &s)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to stat %s with error: %w", path, err)
|
||||
return 1, fmt.Errorf("Failed to stat %s with error: %w", path, err)
|
||||
}
|
||||
if s.Size > int64(opts.MaxFileSize)*1024*1024 {
|
||||
return fmt.Errorf("File size %s is too large for performant editing", humanize.Bytes(uint64(s.Size)))
|
||||
return 1, fmt.Errorf("File size %s is too large for performant editing", humanize.Bytes(uint64(s.Size)))
|
||||
}
|
||||
|
||||
file_data, err := io.ReadAll(read_file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to read from %s with error: %w", path, err)
|
||||
return 1, fmt.Errorf("Failed to read from %s with error: %w", path, err)
|
||||
}
|
||||
read_file.Close()
|
||||
data := strings.Builder{}
|
||||
@@ -199,11 +210,11 @@ func edit_in_kitty(path string, opts *Options) (err error) {
|
||||
add_encoded := func(key, val string) { add(key, encode(val)) }
|
||||
|
||||
if unix.Access(path, unix.R_OK|unix.W_OK) != nil {
|
||||
return fmt.Errorf("%s is not readable and writeable", path)
|
||||
return 1, fmt.Errorf("%s is not readable and writeable", path)
|
||||
}
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to get the current working directory with error: %w", err)
|
||||
return 1, fmt.Errorf("Failed to get the current working directory with error: %w", err)
|
||||
}
|
||||
add_encoded("cwd", cwd)
|
||||
for _, arg := range os.Args[2:] {
|
||||
@@ -219,12 +230,12 @@ func edit_in_kitty(path string, opts *Options) (err error) {
|
||||
}
|
||||
return
|
||||
}
|
||||
err = edit_loop(data.String(), true, write_data)
|
||||
exit_code, err = edit_loop(data.String(), true, write_data)
|
||||
if err != nil {
|
||||
if err == tui.Canceled {
|
||||
return err
|
||||
return 1, err
|
||||
}
|
||||
return fmt.Errorf("Failed to receive edited file back from terminal with error: %w", err)
|
||||
return 1, fmt.Errorf("Failed to receive edited file back from terminal with error: %w", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -265,8 +276,7 @@ func EntryPoint(parent *cli.Command) *cli.Command {
|
||||
if err != nil {
|
||||
return 1, err
|
||||
}
|
||||
err = edit_in_kitty(file_path, &opts)
|
||||
return 0, err
|
||||
return edit_in_kitty(file_path, &opts)
|
||||
},
|
||||
})
|
||||
AddCloneSafeOpts(sc)
|
||||
|
||||
@@ -4,6 +4,7 @@ package tool
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -120,7 +121,13 @@ func confirm_and_run_shebang(args []string, confirm_policy ConfirmPolicy) (rc in
|
||||
editor.Stdin = os.Stdin
|
||||
editor.Stdout = os.Stdout
|
||||
editor.Stderr = os.Stderr
|
||||
editor.Run()
|
||||
if err = editor.Run(); err != nil {
|
||||
var exitErr *exec.ExitError
|
||||
if errors.As(err, &exitErr) {
|
||||
return exitErr.ExitCode(), nil
|
||||
}
|
||||
return 1, err
|
||||
}
|
||||
return confirm_and_run_shebang(args, ConfirmIfNeeded)
|
||||
case "y":
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user