From 0f020d5b371334ec7f0bcba25c0b6e0a6ba04fb3 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 10 Mar 2021 14:27:07 +0530 Subject: [PATCH] When passing a directory or a non-executable file as the program to run to kitty open it with the shell, instead of just failing. Allows using kitty as the program to open directories or shell scripts from desktop environments. --- docs/changelog.rst | 3 +++ kitty/tabs.py | 36 +++++++++++++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 051307a7e..8bd5ba752 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -42,6 +42,9 @@ To update |kitty|, :doc:`follow the instructions `. - Double clicking on empty tab bar area now opens a new tab (:iss:`3201`) +- When passing a directory or a non-executable file as the program to run to + kitty open it with the shell, instead of just failing. + - Linux: Fix rendering of emoji followed by the graphics variation selector not being colored with some fonts (:iss:`3211`) diff --git a/kitty/tabs.py b/kitty/tabs.py index 71b8fee82..546630d46 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -2,6 +2,8 @@ # vim:fileencoding=utf-8 # License: GPL v3 Copyright: 2016, Kovid Goyal +import os +import stat import weakref from collections import deque from contextlib import suppress @@ -15,7 +17,7 @@ from typing import ( from .borders import Borders from .child import Child from .cli_stub import CLIOptions -from .constants import appname +from .constants import appname, kitty_exe from .fast_data_types import ( add_tab, attach_window, detach_window, get_boss, mark_tab_bar_dirty, next_window_id, remove_tab, remove_window, ring_bell, set_active_tab, @@ -277,11 +279,43 @@ class Tab: # {{{ env: Optional[Dict[str, str]] = None, allow_remote_control: bool = False ) -> Child: + check_for_suitability = True if cmd is None: if use_shell: cmd = resolved_shell(self.opts) + check_for_suitability = False else: + if self.args.args: + cmd = list(self.args.args) + else: + cmd = resolved_shell(self.opts) + check_for_suitability = False cmd = self.args.args or resolved_shell(self.opts) + if check_for_suitability: + old_exe = cmd[0] + try: + is_executable = os.access(old_exe, os.X_OK) + except OSError: + pass + else: + try: + st = os.stat(old_exe) + except OSError: + pass + else: + if stat.S_ISDIR(st.st_mode): + cwd = old_exe + cmd = resolved_shell(self.opts) + elif not is_executable: + import shlex + with suppress(OSError): + with open(old_exe) as f: + cmd = [kitty_exe(), '+hold'] + if f.read(2) == '#!': + line = f.read(4096).splitlines()[0] + cmd += shlex.split(line) + [old_exe] + else: + cmd += [resolved_shell(self.opts)[0], cmd[0]] fenv: Dict[str, str] = {} if env: fenv.update(env)