mirror of
https://github.com/prdlk/dotfiles.git
synced 2026-11-02 11:32:42 +00:00
feat(config): add kitty pre‑shutdown session save and config updates
This commit is contained in:
1 parent
cae4fb8766
commit
7e2ae53560
6 files changed
+84
-45
No files matched your search
@@ -8,31 +8,34 @@ One file, three entry points (kitty runs it under a different __name__ each):
|
||||
startup_session at it whenever no running kitty owns today's session (an
|
||||
flock held for the owner's lifetime). Every launch after kitty was closed
|
||||
reopens the day; a second concurrent instance starts plain instead of
|
||||
duplicating today's tabs.
|
||||
duplicating today's tabs. `kitty --session <past day>` (the kitty-days tv
|
||||
cable) opens that day as a read-only snapshot instead.
|
||||
- watcher (__kitty_watcher__): SAVE_DELAY seconds after a window opens,
|
||||
closes, moves, gains focus or finishes a command (and every SAVE_EVERY
|
||||
seconds, for output from long-running programs), rewrites the session this
|
||||
instance started with: tabs/splits/cwds, each shell window's scrollback, and
|
||||
the program running in it. On restore the shell `cat`s the scrollback, then
|
||||
re-runs the program. Days opened later via the picker are left as read-only
|
||||
snapshots.
|
||||
- kitten (map ... kitten day_session.py): picker over every archived day,
|
||||
newest first; the choice is opened with goto_session.
|
||||
re-runs the program.
|
||||
- kitten `day_session.py flush` (sent over remote control by kitty-day-save
|
||||
before shutdown/reboot): saves right away, then holds further saves for
|
||||
FREEZE seconds so windows dying during shutdown can't shrink the saved day.
|
||||
"""
|
||||
import fcntl
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import sys
|
||||
import tempfile
|
||||
from datetime import date, timedelta
|
||||
from time import monotonic
|
||||
|
||||
from kittens.tui.handler import result_handler
|
||||
|
||||
ROOT = os.path.expanduser('~/.local/share/kitty-sessions')
|
||||
SAVE_DELAY = 1 # seconds; batches bursts of events, and the most change lost when kitty dies
|
||||
SAVE_EVERY = 30 # seconds; catches output that arrives without any window event
|
||||
FREEZE = 60 # seconds without saves after a flush; long enough for any shutdown, short if it's cancelled
|
||||
SB_VAR = 'day_scrollback' # user var naming a window's scrollback dump; kitty saves/restores it via launch --var
|
||||
UNSERIALIZE = 'kitty-unserialize-data=' # kitty.constants.unserialize_launch_flag
|
||||
# Never replay a command that was mid-run when the session was saved if any word
|
||||
@@ -149,12 +152,14 @@ def on_load(boss, data) -> None:
|
||||
args[1] = UNSERIALIZE + json.dumps(data)
|
||||
return shlex.join(args)
|
||||
|
||||
frozen_until = 0.0
|
||||
|
||||
def save(timer_id: int) -> None:
|
||||
nonlocal last, pending
|
||||
pending = False
|
||||
windows = [w for w in boss.all_windows if w.created_in_session_name == name]
|
||||
# Nothing left in the session means kitty is closing: keep the last good save.
|
||||
if not windows:
|
||||
if not windows or monotonic() < frozen_until:
|
||||
return
|
||||
os.makedirs(sb_dir, exist_ok=True)
|
||||
keep = set()
|
||||
@@ -185,7 +190,14 @@ def on_load(boss, data) -> None:
|
||||
pending = True
|
||||
add_timer(save, SAVE_DELAY, False)
|
||||
|
||||
def flush() -> None:
|
||||
nonlocal frozen_until
|
||||
frozen_until = 0.0
|
||||
save(0)
|
||||
frozen_until = monotonic() + FREEZE
|
||||
|
||||
_schedule_save = schedule
|
||||
boss.day_session_flush = flush # the flush kitten runs in its own module copy, so it finds this via boss
|
||||
add_timer(lambda timer_id: schedule(), SAVE_EVERY, True)
|
||||
|
||||
|
||||
@@ -206,23 +218,26 @@ def main(args: list[str]) -> None:
|
||||
|
||||
@result_handler(no_ui=True)
|
||||
def handle_result(args: list[str], answer: str, target_window_id: int, boss) -> None:
|
||||
from kitty.session import goto_session
|
||||
if 'flush' in args and (flush := getattr(boss, 'day_session_flush', None)):
|
||||
flush()
|
||||
|
||||
days = sorted(glob.glob(os.path.join(ROOT, '*', '*.kitty-session')), key=os.path.basename, reverse=True)
|
||||
if not days:
|
||||
boss.show_error('No day sessions', f'Nothing archived under {ROOT} yet.')
|
||||
return
|
||||
|
||||
def label(path: str) -> str:
|
||||
d = date.fromisoformat(os.path.basename(path).partition('.')[0])
|
||||
return f'{d} {d:%a} week of {os.path.basename(os.path.dirname(path))}'
|
||||
|
||||
boss.choose_entry('Open a day', ((p, label(p)) for p in days), lambda p: p and goto_session(boss, (p,)))
|
||||
def session_arg() -> str | None:
|
||||
"""`--session` from kitty's own command line (geninclude runs inside kitty)."""
|
||||
argv = sys.argv[1:]
|
||||
for i, a in enumerate(argv):
|
||||
if a.startswith('--session='):
|
||||
return a.partition('=')[2]
|
||||
if a == '--session' and i + 1 < len(argv):
|
||||
return argv[i + 1]
|
||||
return None
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
path = today_path()
|
||||
if own_day(path):
|
||||
other = session_arg()
|
||||
# An explicit --session for another day must not claim today's lock.
|
||||
if (other is None or os.path.abspath(os.path.expanduser(other)) == path) and own_day(path):
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
try:
|
||||
open(path, 'x').close() # empty session = one tab with the default shell
|
||||
|
||||
@@ -35,7 +35,7 @@ action_alias launch_os launch --cwd=current --type=os-window
|
||||
action_alias launch_ov launch --cwd=current --type=overlay
|
||||
action_alias launch_tb launch --cwd=current --type=tab
|
||||
action_alias launch_hs launch --cwd=current --location=hsplit --bias=25
|
||||
action_alias launch_vs launch --cwd=current --location=vsplit --bias=30
|
||||
action_alias launch_vs launch --cwd=current --location=vsplit --bias=33
|
||||
action_alias launch_vs50 launch --cwd=current --location=vsplit --bias=50
|
||||
|
||||
# panes = kitty windows
|
||||
@@ -84,12 +84,10 @@ map alt+o goto_tab 9
|
||||
# reopens today's session unless another running kitty already owns it: tabs,
|
||||
# splits, cwds, each shell's scrollback and the program running in it. Tabs
|
||||
# made with *_with_cwd / --cwd=current join it and are saved ~1s after each
|
||||
# change. ctrl+shift+d reopens any earlier day.
|
||||
# change, and right before shutdown/reboot (kitty-day-save.service). `tv
|
||||
# kitty-days` opens any earlier day in a new kitty.
|
||||
geninclude day_session.py
|
||||
watcher day_session.py
|
||||
map ctrl+shift+d kitten day_session.py
|
||||
# show only the active day's tabs (plus tabs outside any session)
|
||||
tab_bar_filter session:~ or session:^$
|
||||
|
||||
# split nav (vim-aware)
|
||||
map ctrl+h kitten pass_keys.py neighboring_window left ctrl+h
|
||||
@@ -103,9 +101,11 @@ map super+up resize_window taller
|
||||
map super+down resize_window shorter 3
|
||||
|
||||
tab_bar_style powerline
|
||||
tab_powerline_style slanted
|
||||
tab_bar_align center
|
||||
tab_powerline_style angled
|
||||
tab_bar_align left
|
||||
tab_bar_edge top
|
||||
# always shown, even with one tab: it carries the stack-layout pane indicator
|
||||
tab_bar_min_tabs 1
|
||||
|
||||
# Titles come from ~/.config/kitty/tab_bar.py (`{custom}`): the repo the tab's
|
||||
# cwd belongs to, or the file nvim is editing when nvim is the active process.
|
||||
|
||||
Reference in new issue
Block a user