Skip to content

Commit d3c46a8

Browse files
committed
Add action to close a session
1 parent 21fa5bb commit d3c46a8

File tree

6 files changed

+99
-6
lines changed

6 files changed

+99
-6
lines changed

docs/sessions.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ easily swap between them, kitty has you covered. You can use the
6161
In this manner you can define as many projects/sessions as you like and easily
6262
switch between them with a keypress.
6363

64+
You can also close sessions using the :ac:`close_session` action, which closes
65+
all windows in the session with a single keypress.
66+
6467

6568
Displaying the currently active session name
6669
----------------------------------------------

kitty/actions.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class Action(NamedTuple):
2727
'lay': 'Layouts',
2828
'misc': 'Miscellaneous',
2929
'debug': 'Debugging',
30+
'session': 'Sessions',
3031
}
3132
group_title = groups.__getitem__
3233

kitty/boss.py

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,15 @@
122122
from .options.types import Options, nullable_colors
123123
from .options.utils import MINIMUM_FONT_SIZE, KeyboardMode, KeyDefinition
124124
from .os_window_size import initial_window_size_func
125-
from .session import Session, create_sessions, default_save_as_session_opts, get_os_window_sizing_data, goto_session, save_as_session
125+
from .session import (
126+
Session,
127+
close_session_with_confirm,
128+
create_sessions,
129+
default_save_as_session_opts,
130+
get_os_window_sizing_data,
131+
goto_session,
132+
save_as_session,
133+
)
126134
from .shaders import load_shader_programs
127135
from .simple_cli_definitions import grab_keyboard_docs
128136
from .tabs import SpecialWindow, SpecialWindowInstance, Tab, TabDict, TabManager
@@ -1025,7 +1033,7 @@ def mark_window_for_close(self, q: Window | None | int = None) -> None:
10251033
def close_window(self) -> None:
10261034
self.mark_window_for_close(self.window_for_dispatch)
10271035

1028-
def close_windows_with_confirmation_msg(self, windows: Iterable[Window], active_window: Window | None) -> tuple[str, int]:
1036+
def close_windows_with_confirmation_msg(self, windows: Iterable[Window], active_window: Window | None = None) -> tuple[str, int]:
10291037
num_running_programs = 0
10301038
num_background_programs = 0
10311039
count_background = get_options().confirm_os_window_close[1]
@@ -1268,6 +1276,12 @@ def close_tab_no_confirm(self, tab: Tab) -> None:
12681276
for window in tab:
12691277
self.mark_window_for_close(window)
12701278

1279+
def close_windows_no_confirm(self, windows: Sequence[Window]) -> None:
1280+
if self.current_visual_select is not None:
1281+
self.cancel_current_visual_select()
1282+
for window in windows:
1283+
self.mark_window_for_close(window)
1284+
12711285
@ac('win', 'Toggle the fullscreen status of the active OS Window')
12721286
def toggle_fullscreen(self, os_window_id: int = 0) -> None:
12731287
if os_window_id == 0:
@@ -1538,6 +1552,14 @@ def active_session(self) -> str:
15381552
return t.created_in_session_name
15391553
return ''
15401554

1555+
@property
1556+
def all_loaded_session_names(self) -> Iterator[str]:
1557+
seen = set()
1558+
for w in self.all_windows:
1559+
if w.created_in_session_name and w.created_in_session_name not in seen:
1560+
seen.add(w.created_in_session_name)
1561+
yield w.created_in_session_name
1562+
15411563
def refresh_active_tab_bar(self) -> bool:
15421564
tm = self.active_tab_manager
15431565
if tm:
@@ -3057,14 +3079,29 @@ def done2(target_window_id: int, self: Boss) -> None:
30573079
)
30583080
return q if isinstance(q, Window) else None
30593081

3060-
@ac('misc', 'Switch to the specified session, creating it if not already present. See :ref:`goto_session`.')
3082+
@ac('session', 'Switch to the specified session, creating it if not already present. See :ref:`goto_session`.')
30613083
def goto_session(self, *cmdline: str) -> None:
30623084
goto_session(self, cmdline)
30633085

3064-
@ac('misc', 'Save the current kitty state as a session file. See :ref:`save_as_session`.')
3086+
@ac('session', 'Save the current kitty state as a session file. See :ref:`save_as_session`.')
30653087
def save_as_session(self, *cmdline: str) -> None:
30663088
save_as_session(self, cmdline)
30673089

3090+
@ac('session', '''
3091+
Close a session, that is, close all windows that belong to the session.
3092+
Examples::
3093+
# Ask for the session to close
3094+
map f1 close_session
3095+
# Close the currently active session
3096+
map f1 close_session .
3097+
# Close session by name
3098+
map f1 close_session "my session"
3099+
# Close session by path to session file
3100+
map f1 close_session "/path/to/session/file.kitty-session"
3101+
''')
3102+
def close_session(self, *cmdline: str) -> None:
3103+
close_session_with_confirm(self, cmdline)
3104+
30683105
@ac('tab', 'Interactively select a tab to switch to')
30693106
def select_tab(self) -> None:
30703107

kitty/options/utils.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ class InvalidMods(ValueError):
7878
'pass_selection_to_program', 'new_window', 'new_tab', 'new_os_window',
7979
'new_window_with_cwd', 'new_tab_with_cwd', 'new_os_window_with_cwd',
8080
'launch', 'mouse_handle_click', 'show_error', 'goto_session', 'save_as_session',
81+
'close_session',
8182
)
8283
def shlex_parse(func: str, rest: str) -> FuncArgsType:
8384
return func, to_cmdline(rest)

kitty/session.py

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,6 @@ def session_arg_to_name(session_arg: str) -> str:
199199
return session_name
200200

201201

202-
203202
def parse_session(
204203
raw: str, opts: Options, environ: Mapping[str, str] | None = None, session_arg: str = '', session_path: str = ''
205204
) -> Generator[Session, None, None]:
@@ -434,6 +433,58 @@ def get_all_known_sessions() -> dict[str, str]:
434433
return all_known_sessions
435434

436435

436+
def close_session_with_confirm(boss: BossType, cmdline: Sequence[str]) -> None:
437+
if not cmdline:
438+
names = sorted(boss.all_loaded_session_names, key=lambda x: x.lower())
439+
if not names:
440+
boss.ring_bell_if_allowed()
441+
return
442+
if len(names) == 1:
443+
return close_session_with_confirm(boss, names)
444+
def chosen(name: str | None) -> None:
445+
if name:
446+
close_session_with_confirm(boss, (name,))
447+
boss.choose_entry(
448+
_('Select a session to close'), ((name, name) for name in names), chosen)
449+
return
450+
if len(cmdline) != 1:
451+
boss.show_error(_('Invalid close_session specification'), _('{} is not a valid argument to close_session').format(shlex.join(cmdline)))
452+
return
453+
path_or_name = cmdline[0]
454+
if path_or_name == '.':
455+
if name := boss.active_session:
456+
close_session_with_confirm(boss, (name,))
457+
else:
458+
boss.ring_bell_if_allowed()
459+
return
460+
if '/' in path_or_name:
461+
path_to_name = {v: k for k, v in get_all_known_sessions().items()}
462+
name = path_to_name.get(path_or_name, '')
463+
if not name:
464+
boss.ring_bell_if_allowed()
465+
return
466+
else:
467+
name = path_or_name
468+
windows = tuple(w for w in boss.all_windows if w.created_in_session_name == name)
469+
if not windows:
470+
return
471+
msg, num_active_windows = boss.close_windows_with_confirmation_msg(windows, boss.active_window)
472+
x = get_options().confirm_os_window_close[0]
473+
num = num_active_windows if x < 0 else len(windows)
474+
needs_confirmation = x != 0 and num >= abs(x)
475+
476+
def do_close(confirmed: bool) -> None:
477+
if confirmed:
478+
boss.close_windows_no_confirm(windows)
479+
480+
if needs_confirmation:
481+
msg = msg or _('It has {} windows?').format(num)
482+
msg = _('Are you sure you want to close this session?') + ' ' + msg
483+
boss.confirm(msg, do_close, window=boss.active_window, title=_('Close session?'))
484+
else:
485+
do_close(True)
486+
487+
437488
def choose_session(boss: BossType) -> None:
438489
all_known_sessions = get_all_known_sessions()
439490
hmap = {n: len(goto_session_history)-i for i, n in enumerate(goto_session_history)}

kitty/types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ def modmap() -> dict[str, int]:
215215

216216
if TYPE_CHECKING:
217217
from typing import Literal
218-
ActionGroup = Literal['cp', 'sc', 'win', 'tab', 'mouse', 'mk', 'lay', 'misc', 'debug']
218+
ActionGroup = Literal['cp', 'sc', 'win', 'tab', 'mouse', 'mk', 'lay', 'misc', 'debug', 'session']
219219
else:
220220
ActionGroup = str
221221

0 commit comments

Comments
 (0)