Add action to close a session

This commit is contained in:
Kovid Goyal 2025-08-23 09:17:27 +05:30
parent 21fa5bb1ce
commit d3c46a829d
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
6 changed files with 99 additions and 6 deletions

View file

@ -61,6 +61,9 @@ easily swap between them, kitty has you covered. You can use the
In this manner you can define as many projects/sessions as you like and easily
switch between them with a keypress.
You can also close sessions using the :ac:`close_session` action, which closes
all windows in the session with a single keypress.
Displaying the currently active session name
----------------------------------------------

View file

@ -27,6 +27,7 @@ class Action(NamedTuple):
'lay': 'Layouts',
'misc': 'Miscellaneous',
'debug': 'Debugging',
'session': 'Sessions',
}
group_title = groups.__getitem__

View file

@ -122,7 +122,15 @@
from .options.types import Options, nullable_colors
from .options.utils import MINIMUM_FONT_SIZE, KeyboardMode, KeyDefinition
from .os_window_size import initial_window_size_func
from .session import Session, create_sessions, default_save_as_session_opts, get_os_window_sizing_data, goto_session, save_as_session
from .session import (
Session,
close_session_with_confirm,
create_sessions,
default_save_as_session_opts,
get_os_window_sizing_data,
goto_session,
save_as_session,
)
from .shaders import load_shader_programs
from .simple_cli_definitions import grab_keyboard_docs
from .tabs import SpecialWindow, SpecialWindowInstance, Tab, TabDict, TabManager
@ -1025,7 +1033,7 @@ def mark_window_for_close(self, q: Window | None | int = None) -> None:
def close_window(self) -> None:
self.mark_window_for_close(self.window_for_dispatch)
def close_windows_with_confirmation_msg(self, windows: Iterable[Window], active_window: Window | None) -> tuple[str, int]:
def close_windows_with_confirmation_msg(self, windows: Iterable[Window], active_window: Window | None = None) -> tuple[str, int]:
num_running_programs = 0
num_background_programs = 0
count_background = get_options().confirm_os_window_close[1]
@ -1268,6 +1276,12 @@ def close_tab_no_confirm(self, tab: Tab) -> None:
for window in tab:
self.mark_window_for_close(window)
def close_windows_no_confirm(self, windows: Sequence[Window]) -> None:
if self.current_visual_select is not None:
self.cancel_current_visual_select()
for window in windows:
self.mark_window_for_close(window)
@ac('win', 'Toggle the fullscreen status of the active OS Window')
def toggle_fullscreen(self, os_window_id: int = 0) -> None:
if os_window_id == 0:
@ -1538,6 +1552,14 @@ def active_session(self) -> str:
return t.created_in_session_name
return ''
@property
def all_loaded_session_names(self) -> Iterator[str]:
seen = set()
for w in self.all_windows:
if w.created_in_session_name and w.created_in_session_name not in seen:
seen.add(w.created_in_session_name)
yield w.created_in_session_name
def refresh_active_tab_bar(self) -> bool:
tm = self.active_tab_manager
if tm:
@ -3057,14 +3079,29 @@ def done2(target_window_id: int, self: Boss) -> None:
)
return q if isinstance(q, Window) else None
@ac('misc', 'Switch to the specified session, creating it if not already present. See :ref:`goto_session`.')
@ac('session', 'Switch to the specified session, creating it if not already present. See :ref:`goto_session`.')
def goto_session(self, *cmdline: str) -> None:
goto_session(self, cmdline)
@ac('misc', 'Save the current kitty state as a session file. See :ref:`save_as_session`.')
@ac('session', 'Save the current kitty state as a session file. See :ref:`save_as_session`.')
def save_as_session(self, *cmdline: str) -> None:
save_as_session(self, cmdline)
@ac('session', '''
Close a session, that is, close all windows that belong to the session.
Examples::
# Ask for the session to close
map f1 close_session
# Close the currently active session
map f1 close_session .
# Close session by name
map f1 close_session "my session"
# Close session by path to session file
map f1 close_session "/path/to/session/file.kitty-session"
''')
def close_session(self, *cmdline: str) -> None:
close_session_with_confirm(self, cmdline)
@ac('tab', 'Interactively select a tab to switch to')
def select_tab(self) -> None:

View file

@ -78,6 +78,7 @@ class InvalidMods(ValueError):
'pass_selection_to_program', 'new_window', 'new_tab', 'new_os_window',
'new_window_with_cwd', 'new_tab_with_cwd', 'new_os_window_with_cwd',
'launch', 'mouse_handle_click', 'show_error', 'goto_session', 'save_as_session',
'close_session',
)
def shlex_parse(func: str, rest: str) -> FuncArgsType:
return func, to_cmdline(rest)

View file

@ -199,7 +199,6 @@ def session_arg_to_name(session_arg: str) -> str:
return session_name
def parse_session(
raw: str, opts: Options, environ: Mapping[str, str] | None = None, session_arg: str = '', session_path: str = ''
) -> Generator[Session, None, None]:
@ -434,6 +433,58 @@ def get_all_known_sessions() -> dict[str, str]:
return all_known_sessions
def close_session_with_confirm(boss: BossType, cmdline: Sequence[str]) -> None:
if not cmdline:
names = sorted(boss.all_loaded_session_names, key=lambda x: x.lower())
if not names:
boss.ring_bell_if_allowed()
return
if len(names) == 1:
return close_session_with_confirm(boss, names)
def chosen(name: str | None) -> None:
if name:
close_session_with_confirm(boss, (name,))
boss.choose_entry(
_('Select a session to close'), ((name, name) for name in names), chosen)
return
if len(cmdline) != 1:
boss.show_error(_('Invalid close_session specification'), _('{} is not a valid argument to close_session').format(shlex.join(cmdline)))
return
path_or_name = cmdline[0]
if path_or_name == '.':
if name := boss.active_session:
close_session_with_confirm(boss, (name,))
else:
boss.ring_bell_if_allowed()
return
if '/' in path_or_name:
path_to_name = {v: k for k, v in get_all_known_sessions().items()}
name = path_to_name.get(path_or_name, '')
if not name:
boss.ring_bell_if_allowed()
return
else:
name = path_or_name
windows = tuple(w for w in boss.all_windows if w.created_in_session_name == name)
if not windows:
return
msg, num_active_windows = boss.close_windows_with_confirmation_msg(windows, boss.active_window)
x = get_options().confirm_os_window_close[0]
num = num_active_windows if x < 0 else len(windows)
needs_confirmation = x != 0 and num >= abs(x)
def do_close(confirmed: bool) -> None:
if confirmed:
boss.close_windows_no_confirm(windows)
if needs_confirmation:
msg = msg or _('It has {} windows?').format(num)
msg = _('Are you sure you want to close this session?') + ' ' + msg
boss.confirm(msg, do_close, window=boss.active_window, title=_('Close session?'))
else:
do_close(True)
def choose_session(boss: BossType) -> None:
all_known_sessions = get_all_known_sessions()
hmap = {n: len(goto_session_history)-i for i, n in enumerate(goto_session_history)}

View file

@ -215,7 +215,7 @@ def modmap() -> dict[str, int]:
if TYPE_CHECKING:
from typing import Literal
ActionGroup = Literal['cp', 'sc', 'win', 'tab', 'mouse', 'mk', 'lay', 'misc', 'debug']
ActionGroup = Literal['cp', 'sc', 'win', 'tab', 'mouse', 'mk', 'lay', 'misc', 'debug', 'session']
else:
ActionGroup = str