Select MRU On CloseΒΆ
When the current tab or session closes, this script selects the next most recently used tab or session.
#!/usr/bin/env python3.7
#
# This script selects the most recently used tab or split pane when the current
# tab or split pane closes.
import iterm2
window_id_to_tab_ids = {}
tab_id_to_session_ids = {}
def init_window(window):
"""Initialize the window -> tab list for one window"""
global window_id_to_tab_ids
window_id_to_tab_ids[window.window_id] = list(map(lambda x: x.tab_id, window.tabs))
for tab in window.tabs:
init_tab(tab)
def init_tab(tab):
"""Initialize the tab -> session list for one tab"""
global tab_id_to_session_ids
tab_id_to_session_ids[tab.tab_id] = list(map(lambda x: x.session_id, tab.sessions))
def init_window_if_needed(w):
"""Record the tab order in window w if it doesn't already exist."""
global window_id_to_tab_ids
if w.window_id in window_id_to_tab_ids:
return
init_window(w)
def init_tab_if_needed(t):
"""Record the tab order in tab t if it doesn't already exist."""
global tab_id_to_session_ids
if t.tab_id in tab_id_to_session_ids:
return
init_tab(t)
def refresh_window(window):
"""Remove defunct tabs and add new tabs to tab list for window"""
global window_id_to_tab_ids
tab_ids = list(map(lambda x: x.tab_id, window.tabs))
# Remove defunct tabs
if window.window_id in window_id_to_tab_ids:
existing = window_id_to_tab_ids[window.window_id]
else:
existing = []
updated = list(filter(lambda x: x in tab_ids, existing))
# Add any newly discovered tabs to the end
for t in window.tabs:
if t.tab_id not in updated:
updated.append(t.tab_id)
window_id_to_tab_ids[window.window_id] = updated
def refresh_tab(tab):
"""Remove defunct sessions and add new sessions to session list for tab"""
global tab_id_to_session_ids
session_ids = list(map(lambda x: x.session_id, tab.sessions))
# Remove defunct sessions
if tab.tab_id in tab_id_to_session_ids:
existing = tab_id_to_session_ids[tab.tab_id]
else:
existing = []
updated = list(filter(lambda x: x in session_ids, existing))
# Add any newly discovered sessions to the end
for s in tab.sessions:
if s.session_id not in updated:
updated.append(s.session_id)
tab_id_to_session_ids[tab.tab_id] = updated
def get_mru_tab_id(window):
"""Returns the most recently used tab ID in this window"""
global window_id_to_tab_ids
if window.window_id not in window_id_to_tab_ids:
return None
tab_ids = window_id_to_tab_ids[window.window_id]
if len(tab_ids) == 0:
return None
return tab_ids[0]
def get_mru_session_id(tab):
"""Returns the most recently used session ID in this tab"""
global tab_id_to_session_ids
if tab.tab_id not in tab_id_to_session_ids:
return None
session_ids = tab_id_to_session_ids[tab.tab_id]
if len(session_ids) == 0:
return None
return session_ids[0]
def get_successor_tab_id(window, tab_id):
"""When a tab is closed, select the next most recently used tab. Remove any defunct tabs from the MRU list."""
refresh_window(window)
mru_tab_id = get_mru_tab_id(window)
if not mru_tab_id:
return None
if mru_tab_id == tab_id:
return None
return mru_tab_id
def get_successor_session_id(session, tab):
"""When a session is closed, select the next most recently used session. Remove any defunct sessions from the MRU list."""
refresh_tab(tab)
mru_session_id = get_mru_session_id(tab)
if not mru_session_id:
return None
if mru_session_id == session.session_id:
return None
return mru_session_id
def update_mru_tab(window_id, tab_id):
"""When a tab gets selected, move it to the head of the MRU list"""
global window_id_to_tab_ids
if window_id in window_id_to_tab_ids:
ids = window_id_to_tab_ids[window_id]
else:
ids = []
if tab_id in ids:
i = ids.index(tab_id)
del ids[i]
ids.insert(0, tab_id)
window_id_to_tab_ids[window_id] = ids
def update_mru_session(tab_id, session_id):
"""When a session gets selected, move it to the head of the MRU list"""
global tab_id_to_session_ids
if tab_id in tab_id_to_session_ids:
ids = tab_id_to_session_ids[tab_id]
else:
ids = []
if session_id in ids:
i = ids.index(session_id)
del ids[i]
ids.insert(0, session_id)
tab_id_to_session_ids[tab_id] = ids
def tab_known(tab_id, window):
"""Do we already know about this tab and window combination?"""
global window_id_to_tab_ids
if window.window_id not in window_id_to_tab_ids:
return False
return tab_id in window_id_to_tab_ids[window.window_id]
def session_known(session_id, tab):
"""Do we already know about this session and tab combination?"""
global tab_id_to_session_ids
if tab.tab_id not in tab_id_to_session_ids:
return False
return session_id in tab_id_to_session_ids[tab.tab_id]
def window_has_closed_tabs(window):
"""Are there tab IDs in the MRU list not in the actual set of tabs?"""
global window_id_to_tab_ids
actual_tab_ids = list(map(lambda x: x.tab_id, window.tabs))
for mru_tab_id in window_id_to_tab_ids[window.window_id]:
if mru_tab_id not in actual_tab_ids:
return True
return False
def tab_has_closed_sessions(tab):
"""Are there session IDs in the MRU list not in the actual set of sessions?"""
global tab_id_to_session_ids
actual_session_ids = list(map(lambda x: x.session_id, tab.sessions))
for mru_session_id in tab_id_to_session_ids[tab.tab_id]:
if mru_session_id not in actual_session_ids:
return True
return False
def add_tab_to_window(window_id, tab_id):
"""Add a tab ID to the MRU list for a window."""
global window_id_to_tab_ids
if window_id in window_id_to_tab_ids:
ids = window_id_to_tab_ids[window_id]
else:
ids = []
ids.insert(0, tab_id)
window_id_to_tab_ids[window_id] = ids
def add_session_to_tab(tab_id, session_id):
"""Add a session ID to the MRU list for a tab."""
global tab_id_to_session_ids
if tab_id in tab_id_to_session_ids:
ids = tab_id_to_session_ids[tab_id]
else:
ids = []
ids.insert(0, session_id)
tab_id_to_session_ids[tab_id] = ids
async def main(connection):
app = await iterm2.async_get_app(connection)
for window in app.terminal_windows:
init_window(window)
async def handle_close_tab(window, tab_id):
"""A tab was closed"""
mru_tab_id = get_successor_tab_id(window, tab_id)
if not mru_tab_id:
return
tab = app.get_tab_by_id(mru_tab_id)
if tab:
await tab.async_select()
async def handle_close_session(session, tab):
"""A session was closed"""
mru_session_id = get_successor_session_id(session, tab)
if not mru_session_id:
return
session = app.get_session_by_id(mru_session_id)
if session:
await session.async_activate()
async def handle_selected_tab_changed(tab_id):
"""The selected tab changed"""
tab = app.get_tab_by_id(update.selected_tab_changed.tab_id)
if not tab:
return
window = app.get_window_for_tab(tab_id)
if not window:
return
init_tab_if_needed(tab)
init_window_if_needed(window)
if not tab_known(tab_id, window):
add_tab_to_window(window.window_id, tab_id)
return
if window_has_closed_tabs(window):
await handle_close_tab(window, tab_id)
else:
update_mru_tab(window.window_id, tab_id)
def handle_window_became_key(window_id):
"""A window got keyboard focus"""
w = app.get_window_by_id(window_id)
if w:
init_window_if_needed(w)
async def handle_session_selected(session_id):
"""The selected session changed"""
s = app.get_session_by_id(session_id)
if not s:
return
window, tab = app.get_tab_and_window_for_session(s)
if not tab:
return
init_tab_if_needed(tab)
init_window_if_needed(window)
if not session_known(session_id, tab):
add_session_to_tab(tab.tab_id, s.session_id)
return
if tab_has_closed_sessions(tab):
await handle_close_session(s, tab)
else:
update_mru_session(tab.tab_id, s.session_id)
# Watch for changes to keyboard focus and update state and active tab/session as needed.
async with iterm2.FocusMonitor(connection) as monitor:
while True:
update = await monitor.async_get_next_update()
if update.selected_tab_changed:
await handle_selected_tab_changed(update.selected_tab_changed.tab_id)
continue
if update.active_session_changed:
await handle_session_selected(update.active_session_changed.session_id)
continue
if (update.window_changed and
update.window_changed.event == iterm2.FocusUpdateWindowChanged.Reason.TERMINAL_WINDOW_BECAME_KEY):
handle_window_became_key(update.window_changed.window_id)
continue
iterm2.run_forever(main)