戰(zhàn):用 copycolor 腳本在分屏?xí)r保持標(biāo)簽頁(yè)顏色同步)
iTerm2 Python API 實(shí)戰(zhàn)用 copycolor 腳本在分屏?xí)r保持標(biāo)簽頁(yè)顏色同步【免費(fèi)下載鏈接】iTerm2iTerm2 is a terminal emulator for Mac OS X that does amazing things.項(xiàng)目地址: https://gitcode.com/gh_mirrors/it/iTerm2導(dǎo)讀在 iTerm2 中同一標(biāo)簽頁(yè)Tab內(nèi)的多個(gè)窗格Pane可以來(lái)自不同的 Profile如果這些 Profile 定義了不同的標(biāo)簽頁(yè)顏色分屏后就會(huì)出現(xiàn)同一標(biāo)簽頁(yè)內(nèi)兩個(gè)窗格顏色不一致的情況。iTerm2 官方 Python API 示例 copycolor.rst 提供了一段名為 Preserve Tab Color保持標(biāo)簽頁(yè)顏色的守護(hù)腳本它監(jiān)聽(tīng)新會(huì)話的創(chuàng)建自動(dòng)把標(biāo)簽頁(yè)內(nèi)已有會(huì)話的標(biāo)簽顏色復(fù)制給新窗格并在不改動(dòng)底層 Profile 的前提下完成這一修改。讀完本文你將掌握LocalWriteOnlyProfile、NewSessionMonitor、async_set_profile_properties等核心 API 的用法并能直接部署這段腳本到自己的 iTerm2 環(huán)境中。問(wèn)題背景分屏后標(biāo)簽頁(yè)顏色為什么會(huì)對(duì)不上iTerm2 允許每個(gè) Profile 單獨(dú)配置標(biāo)簽頁(yè)顏色Tab Color。當(dāng)你執(zhí)行用其他 Profile 在此處分割Split with a different profile操作時(shí)新窗格會(huì)繼承該 Profile 的標(biāo)簽頁(yè)顏色而舊窗格保留原 Profile 的顏色。如果兩個(gè) Profile 的顏色不同同一個(gè)標(biāo)簽頁(yè)就會(huì)呈現(xiàn)兩種顏色視覺(jué)上容易被誤認(rèn)為兩個(gè)獨(dú)立的標(biāo)簽頁(yè)。官方示例開篇即點(diǎn)明了這一場(chǎng)景If you split a session using a different profile, you can end up with two panes that have different tab colors. This script copies the old tab color to the new split pane so they stay in sync.解決思路很直接監(jiān)聽(tīng)新會(huì)話創(chuàng)建事件找到它所在的標(biāo)簽頁(yè)讀取該標(biāo)簽頁(yè)中已經(jīng)存在的會(huì)話的標(biāo)簽顏色再用會(huì)話級(jí)而非 Profile 級(jí)的寫操作把顏色應(yīng)用到新會(huì)話上。這段腳本的核心價(jià)值在于第二個(gè)技巧——如何修改會(huì)話的 Profile 屬性而不修改底層 Profile因?yàn)橹苯痈牡讓?Profile 會(huì)影響所有使用該 Profile 的會(huì)話。完整示例代碼以下是 copycolor.rst 中的完整腳本約 44 行你可以從倉(cāng)庫(kù)的 copycolor.its 下載該腳本的原始版本#!/usr/bin/env python3.7 import iterm2 async def main(connection): async def async_color_in_tab(tab, exclude): Return the tab color of any session that already existed. colors [] for session in tab.sessions: if session exclude: continue profile await session.async_get_profile() if not profile: continue color profile.tab_color if color: return color return None app await iterm2.async_get_app(connection) async with iterm2.NewSessionMonitor(connection) as mon: while True: # Wait for a new session to be created session_id await mon.async_get() session app.get_session_by_id(session_id) if not session: continue window, tab app.get_tab_and_window_for_session(session) if not tab: continue color await async_color_in_tab(tab, session) if not color: continue # Another session had a color in this tab. Change the tab # color property of the new session. Use LocalWriteOnlyProfile # and session.async_set_profile_properties to avoid changing the # underlying profile. change iterm2.LocalWriteOnlyProfile() change.set_tab_color(color) await session.async_set_profile_properties(change) iterm2.run_forever(main)腳本結(jié)構(gòu)清晰自頂向下可以分為四個(gè)部分讀取已有顏色、監(jiān)聽(tīng)新會(huì)話、查找所屬標(biāo)簽頁(yè)、寫入顏色。下面逐段剖析。逐段剖析四個(gè)核心步驟1. 讀取標(biāo)簽頁(yè)中已有會(huì)話的顏色async def async_color_in_tab(tab, exclude): Return the tab color of any session that already existed. colors [] for session in tab.sessions: if session exclude: continue profile await session.async_get_profile() if not profile: continue color profile.tab_color if color: return color return Noneasync_color_in_tab遍歷標(biāo)簽頁(yè)內(nèi)的所有會(huì)話tab.sessions跳過(guò)新創(chuàng)建的那個(gè)會(huì)話exclude對(duì)每個(gè)已有會(huì)話調(diào)用session.async_get_profile()獲取其生效的 Profile讀取profile.tab_color。一旦找到非空顏色就立即返回全部沒(méi)有顏色則返回None。兩個(gè)細(xì)節(jié)值得注意async_get_profile()返回的 Profile包含會(huì)話本地修改。從 session.py 的實(shí)現(xiàn)看它通過(guò)iterm2.rpc.async_get_profile拉取數(shù)據(jù)docstring 明確寫著including any session-local changes not in the underlying profile包含不在底層 Profile 中的會(huì)話本地修改。這意味著即使舊窗格此前已被腳本改過(guò)顏色新窗格也能讀到最終生效的顏色而不是回退到 Profile 原始值。函數(shù)內(nèi)部聲明了colors []但從未使用屬于示例代碼遺留的冗余變量不影響邏輯可自行刪除。2. 監(jiān)聽(tīng)新會(huì)話的創(chuàng)建app await iterm2.async_get_app(connection) async with iterm2.NewSessionMonitor(connection) as mon: while True: # Wait for a new session to be created session_id await mon.async_get() session app.get_session_by_id(session_id) if not session: continueiterm2.async_get_app(connection)獲取當(dāng)前 App 的對(duì)象模型包含窗口、標(biāo)簽頁(yè)、會(huì)話的樹狀結(jié)構(gòu)。NewSessionMonitor是一個(gè)異步上下文管理器進(jìn)入時(shí)訂閱新會(huì)話創(chuàng)建通知退出時(shí)自動(dòng)取消訂閱見(jiàn) lifecycle.py 的實(shí)現(xiàn)__aenter__中調(diào)用async_subscribe_to_new_session_notification__aexit__中調(diào)用async_unsubscribe。mon.async_get()會(huì)阻塞等待下一個(gè)新會(huì)話事件返回新會(huì)話的 ID字符串。隨后用app.get_session_by_id(session_id)把 ID 解析為Session對(duì)象。如果解析失敗例如會(huì)話已被關(guān)閉直接continue等待下一個(gè)事件。整個(gè)while True循環(huán)讓腳本變成一個(gè)常駐守護(hù)進(jìn)程這正是run_forever入口的意義。3. 定位新會(huì)話所屬的標(biāo)簽頁(yè)window, tab app.get_tab_and_window_for_session(session) if not tab: continueget_tab_and_window_for_session返回(Window, Tab)二元組用于找出會(huì)話所屬的窗口和標(biāo)簽頁(yè)。值得注意的是從 app.py 的源碼注釋看這個(gè)方法已標(biāo)記為 Deprecated理由是名字與返回參數(shù)順序不符——它實(shí)際返回的是(Window, Tab)而非名字暗示的(Tab, Window)官方建議使用新方法get_window_and_tab_for_session。內(nèi)部實(shí)現(xiàn)是遍歷terminal_windows與window.tabs檢查session in tab.all_sessions來(lái)定位。新代碼中可以改用get_window_and_tab_for_session語(yǔ)義更清晰。如果找不到所屬標(biāo)簽頁(yè)例如會(huì)話已被關(guān)閉跳過(guò)本次事件。4. 通過(guò) LocalWriteOnlyProfile 寫入顏色color await async_color_in_tab(tab, session) if not color: continue # Another session had a color in this tab. Change the tab # color property of the new session. Use LocalWriteOnlyProfile # and session.async_set_profile_properties to avoid changing the # underlying profile. change iterm2.LocalWriteOnlyProfile() change.set_tab_color(color) await session.async_set_profile_properties(change)這是全腳本最關(guān)鍵的部分不修改底層 Profile只修改這一個(gè)會(huì)話的生效配置。LocalWriteOnlyProfile是一個(gè)可寫不可讀的臨時(shí) Profile 對(duì)象從 profile.py 的實(shí)現(xiàn)看它的values屬性保存著一組(鍵, JSON 值)對(duì)本身并不與任何真實(shí) Profile 綁定。change.set_tab_color(color)會(huì)調(diào)用_color_set(Tab Color, value)把顏色對(duì)象序列化為 JSON 存進(jìn)values字典。session.async_set_profile_properties(change)負(fù)責(zé)把這份變更應(yīng)用到會(huì)話。從 session.py 的實(shí)現(xiàn)看當(dāng)連接支持多屬性批量設(shè)置supports_multiple_set_profile_properties時(shí)一次性把所有(key, json_value)打包為 assignments通過(guò)async_set_profile_properties_json發(fā)送對(duì)不支持的老版本3.3.0beta9 及更早則退化為逐條調(diào)用async_set_profile_property_json任一屬性寫入失敗都會(huì)拋出RPCException。其 docstring 明確說(shuō)明When you use this function the underlying profile is not modified. The session will keep a copy of its profile with these modifications.使用此函數(shù)時(shí)底層 Profile 不會(huì)被修改會(huì)話會(huì)保留一份帶這些修改的 Profile 副本。這正是腳本能只影響新窗格、不影響其他會(huì)話的根本原因。關(guān)鍵 API 源碼級(jí)剖析LocalWriteOnlyProfile會(huì)話級(jí)臨時(shí)配置LocalWriteOnlyProfile是 iTerm2 Python API 中實(shí)現(xiàn)局部覆蓋的標(biāo)準(zhǔn)工具官方文檔在 profile.py 中直接引用本示例作為用法范例。它配合Session.async_set_profile_properties使用相關(guān)方法包括方法作用底層鍵名set_tab_color(color)設(shè)置標(biāo)簽頁(yè)顏色未啟用明暗分離顏色時(shí)生效Tab Colorset_tab_color_light(color)設(shè)置明色模式變體啟用明暗分離顏色時(shí)生效Tab Color (Light)set_tab_color_dark(color)設(shè)置暗色模式變體啟用明暗分離顏色時(shí)生效Tab Color (Dark)set_use_tab_color(value)是否啟用標(biāo)簽頁(yè)顏色Use Tab Color顏色對(duì)象通過(guò)_color_set內(nèi)部的value.get_dict()序列化為 JSON 存儲(chǔ)。值得注意的是set_tab_color的 docstring 特別說(shuō)明它僅在未啟用獨(dú)立明暗模式顏色時(shí)使用used only when separate light/dark mode colors are not enabled。如果目標(biāo) Profile 啟用了Use separate colors for light and dark mode選項(xiàng)標(biāo)簽頁(yè)顏色實(shí)際由Tab Color (Light)/Tab Color (Dark)決定此時(shí)應(yīng)改用set_tab_color_light/set_tab_color_dark才能生效——這是本示例在特定配置下的一個(gè)擴(kuò)展點(diǎn)。Color顏色值的表示iterm2.Color見(jiàn) color.py以 RGBA 描述顏色r、g、b、a取值均為 0-255a默認(rèn) 255color_space目前僅支持 sRGB。它還提供from_hex解析#aabbcc或 13 位十六進(jìn)制與from_cocoa解析 NSKeyedArchiver 編碼等工廠方法。本示例中顏色直接取自已有 Profile 的tab_color屬性屬于Color實(shí)例無(wú)需自行構(gòu)造。NewSessionMonitor事件驅(qū)動(dòng)的守護(hù)模式NewSessionMonitorlifecycle.py內(nèi)部使用asyncio.Queue緩存通知async_get()從隊(duì)列取出新會(huì)話 ID 并返回。它與其他 Monitor如SessionTerminationMonitor、LayoutChangeMonitor一樣遵循async with上下文管理器協(xié)議退出時(shí)自動(dòng)退訂。配合iterm2.run_forever(main)入口見(jiàn) connection.py腳本可以無(wú)限期運(yùn)行這正是官方守護(hù)腳本的典型形態(tài)。對(duì)照實(shí)驗(yàn)settabcolor 示例帶來(lái)的啟發(fā)同目錄下的另一個(gè)官方示例 settabcolor.rst 與本腳本形成了很好的對(duì)照app await iterm2.async_get_app(connection) session app.current_terminal_window.current_tab.current_session change iterm2.LocalWriteOnlyProfile() color iterm2.Color(255, 128, 128) change.set_tab_color(color) change.set_use_tab_color(True) await session.async_set_profile_properties(change)settabcolor 演示的是主動(dòng)設(shè)置對(duì)當(dāng)前會(huì)話寫入硬編碼顏色并額外調(diào)用change.set_use_tab_color(True)開啟標(biāo)簽頁(yè)顏色開關(guān)最后用run_until_complete一次性執(zhí)行。而 copycolor 是被動(dòng)同步它只復(fù)制顏色值本身。對(duì)比兩者可以得出一個(gè)重要結(jié)論——copycolor 假設(shè)已有會(huì)話的標(biāo)簽頁(yè)顏色已經(jīng)處于生效狀態(tài)即舊 Profile 已開啟Use Tab Color新窗格沿用相同的顏色值時(shí)自然沿用開關(guān)狀態(tài)若舊窗格顏色來(lái)自關(guān)閉開關(guān)的狀態(tài)即未啟用標(biāo)簽顏色則profile.tab_color與渲染結(jié)果不一定一致這是使用本腳本時(shí)需要了解的邊界條件。若需在新窗格上強(qiáng)制開啟標(biāo)簽顏色可參考 settabcolor 追加change.set_use_tab_color(True)。安裝與運(yùn)行腳本以#!/usr/bin/env python3.7開頭要求 Python 3.7 及以上版本當(dāng)前 iTerm2 的 Python API 運(yùn)行時(shí)通常內(nèi)置 Python 3.10兼容無(wú)虞。運(yùn)行方式有兩種從倉(cāng)庫(kù)下載腳本文件copycolor.its 是該示例的官方可下載版本可以通過(guò) iTerm2 菜單Scripts Manage Import...導(dǎo)入.its 即 iTerm2 Script 文件的擴(kuò)展名。手動(dòng)創(chuàng)建腳本新建一個(gè)以.py結(jié)尾的文件粘貼上文完整代碼放入~/Library/Application Support/iTerm2/Scripts/AutoLaunch/目錄即可隨 iTerm2 啟動(dòng)自動(dòng)運(yùn)行或放入Scripts/目錄后從Scripts菜單手動(dòng)啟動(dòng)。由于腳本主體是while True循環(huán) run_forever(main)它屬于常駐守護(hù)腳本啟動(dòng)后持續(xù)監(jiān)聽(tīng)直到 iTerm2 退出或腳本被手動(dòng)終止。分屏操作完成后新窗格的標(biāo)簽顏色會(huì)自動(dòng)與同標(biāo)簽頁(yè)內(nèi)已有會(huì)話對(duì)齊之后無(wú)需再手動(dòng)干預(yù)。關(guān)于 Python API 腳本的更多運(yùn)行機(jī)制普通腳本與守護(hù)腳本的區(qū)別、調(diào)試方法可以繼續(xù)閱讀倉(cāng)庫(kù)中的 tutorial/index.rst、running.rst 與 daemons.rst。擴(kuò)展思路與注意事項(xiàng)顏色繼承方向腳本總是優(yōu)先取tab.sessions中第一個(gè)按會(huì)話順序有顏色的會(huì)話作為顏色來(lái)源。若同一標(biāo)簽頁(yè)內(nèi)多個(gè)窗格顏色本就不同新窗格會(huì)繼承最先遇到的那個(gè)方向不可配置。明暗分離模式若舊窗格 Profile 啟用了獨(dú)立明暗顏色腳本讀取到的tab_color可能為空此時(shí)需要改用tab_color_light/tab_color_dark屬性并對(duì)應(yīng)調(diào)用set_tab_color_light/set_tab_color_dark兩者均已在LocalWriteOnlyProfile中提供見(jiàn) profile.py。臨時(shí)會(huì)話與 tmux 會(huì)話腳本通過(guò)app.get_session_by_id解析會(huì)話對(duì)任何新創(chuàng)建會(huì)話一視同仁如果session_id無(wú)法解析為 Session 對(duì)象如會(huì)話已關(guān)閉腳本會(huì)安全跳過(guò)不會(huì)崩潰。API 版本兼容性async_set_profile_properties內(nèi)部會(huì)自動(dòng)檢測(cè)連接能力對(duì)不支持批量寫入的老版本 iTerm23.3.0beta9 及更早退化為逐條寫入因此腳本具備較好的向后兼容性。與新方法的關(guān)系get_tab_and_window_for_session已廢棄新代碼建議改用get_window_and_tab_for_session返回順序同為(Window, Tab)??偠灾甤opycolor 腳本是一份麻雀雖小、五臟俱全的官方范例它完整演示了事件監(jiān)聽(tīng)NewSessionMonitor 對(duì)象模型查詢async_get_app/get_session_by_id/get_tab_and_window_for_session 局部寫入LocalWriteOnlyProfileasync_set_profile_properties這一 iTerm2 Python API 的典型編程范式尤其適合作為學(xué)習(xí)在不污染底層 Profile 的前提下做會(huì)話級(jí)定制的入門教材。【免費(fèi)下載鏈接】iTerm2iTerm2 is a terminal emulator for Mac OS X that does amazing things.項(xiàng)目地址: https://gitcode.com/gh_mirrors/it/iTerm2創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考