local wezterm = require("wezterm") local config = wezterm.config_builder() -- 구버전 wezterm이면 config = {} 로 바꾸세요 local act = wezterm.action -- ============================================================ -- 폰트 & 테마 -- ============================================================ config.font = wezterm.font_with_fallback({ "JetBrains Mono", -- 원하는 폰트로 교체 (Nerd Font 계열 추천: JetBrainsMono Nerd Font 등) "Noto Sans Mono CJK KR", -- 한글 fallback }) config.font_size = 13.0 config.line_height = 1.1 config.color_scheme = "Catppuccin Mocha" -- wezterm show-config 나 https://wezterm.org/colorschemes/ 에서 이름 확인 -- 색상표를 직접 지정하고 싶으면 color_scheme 대신 아래처럼: -- config.colors = { -- foreground = "#c0caf5", -- background = "#1a1b26", -- cursor_bg = "#c0caf5", -- cursor_border = "#c0caf5", -- } config.window_background_opacity = 0.92 -- 1.0이면 완전 불투명 config.macos_window_background_blur = 20 -- macOS 전용, 배경 블러 config.text_background_opacity = 1.0 -- ============================================================ -- 창 & 탭바 레이아웃 -- ============================================================ config.window_padding = { left = 8, right = 8, top = 8, bottom = 8, } config.initial_cols = 140 config.initial_rows = 40 config.use_fancy_tab_bar = false -- true: OS 스타일 탭바 / false: 레트로(경량) 탭바 config.tab_bar_at_bottom = false -- 탭바/상태바를 터미널 상단에 표시 config.hide_tab_bar_if_only_one_tab = false -- 탭이 하나뿐이어도 탭바를 항상 표시 config.show_new_tab_button_in_tab_bar = true config.window_decorations = "TITLE | RESIZE" -- 상단 타이틀바 + 리사이즈 테두리 표시 -- ------------------------------------------------------------ -- [MobaXterm 스타일] 호스트(도메인)별 탭 색상 자동 구분 -- 같은 SSH 호스트/도메인에서 연 탭은 항상 같은 색으로 표시됨 -- ------------------------------------------------------------ local host_colors = { "#f38ba8", "#fab387", "#f9e2af", "#a6e3a1", "#94e2d5", "#89b4fa", "#cba6f7", "#f5c2e7", } local function color_for_host(name) local hash = 0 for i = 1, #name do hash = (hash * 31 + string.byte(name, i)) % 100000 end return host_colors[(hash % #host_colors) + 1] end -- ------------------------------------------------------------ -- [1] 탭 제목 커스터마이징 -- 탭에 "인덱스: 실행중인 프로세스명 [호스트]" 형식으로 표시 -- local(로컬 셸)이면 회색조, SSH 도메인이면 호스트별 고유 색 -- ------------------------------------------------------------ wezterm.on("format-tab-title", function(tab, tabs, panes, config, hover, max_width) local process = tab.active_pane.foreground_process_name or "" local name = process:match("([^/\\]+)$") or process if name == "" then name = tab.active_pane.title end local domain = tab.active_pane.domain_name or "local" local title = string.format(" %d: %s [%s] ", tab.tab_index + 1, name, domain) local accent = domain == "local" and "#6c7086" or color_for_host(domain) if tab.is_active then return { { Background = { Color = accent } }, { Foreground = { Color = "#1e1e2e" } }, { Text = title }, } end return { { Foreground = { Color = accent } }, { Text = title }, } end) -- ------------------------------------------------------------ -- [MobaXterm 스타일] 우측 상태바: 접속 호스트 + 워크스페이스 + 접속 경과시간 + 날짜/시간 -- MobaXterm 하단 상태바처럼 "지금 어디에 붙어있는지"를 한눈에 표시 -- ------------------------------------------------------------ local pane_start_times = {} wezterm.on("update-right-status", function(window, pane) window:set_left_status(wezterm.format({ { Background = { Color = "#89b4fa" } }, { Foreground = { Color = "#1e1e2e" } }, { Text = " ☰ Menu " }, { Background = { Color = "#313244" } }, { Foreground = { Color = "#cdd6f4" } }, { Text = " Alt+Click / Ctrl+a m " }, })) local pane_id = pane:pane_id() if not pane_start_times[pane_id] then pane_start_times[pane_id] = os.time() end local elapsed = os.time() - pane_start_times[pane_id] local elapsed_str = string.format("%02d:%02d:%02d", elapsed // 3600, (elapsed % 3600) // 60, elapsed % 60) local domain = pane:get_domain_name() or "local" local workspace = window:active_workspace() local date = wezterm.strftime("%Y-%m-%d %H:%M:%S") window:set_right_status(wezterm.format({ { Foreground = { Color = color_for_host(domain) } }, { Text = " " .. domain .. " " }, { Foreground = { Color = "#89b4fa" } }, { Text = "| " .. workspace .. " " }, { Foreground = { Color = "#f9e2af" } }, { Text = "| " .. elapsed_str .. " " }, { Foreground = { Color = "#cdd6f4" } }, { Text = "| " .. date .. " " }, })) end) -- ============================================================ -- [2] 세션 / 멀티플렉싱 -- ============================================================ -- SSH 도메인: 자주 접속하는 원격 서버를 wezterm이 네이티브로 관리 -- 실제 서버 정보가 준비되면 아래 예시를 복사해서 채우세요. config.ssh_domains = { -- { -- name = "myserver", -- remote_address = "myserver.example.com", -- username = "youruser", -- }, } -- Unix domain (mux server): wezterm을 서버로 띄워두면 터미널 껐다 켜도 세션 유지 -- 사용법: 먼저 `wezterm-mux-server --daemonize` 실행 후, 클라이언트에서 아래 도메인에 접속 config.unix_domains = { { name = "unix" }, } local is_windows = wezterm.target_triple:find("windows") ~= nil local config_file = wezterm.config_file or (is_windows and (wezterm.home_dir .. "\\.wezterm.lua") or "/mnt/storage/wezterm.lua") local function notify(window, message) if window.toast_notification then window:toast_notification("WezTerm", message, nil, 4000) else wezterm.log_info(message) end end local function read_config_text() local file = io.open(config_file, "r") if not file then return nil, "설정 파일을 열 수 없습니다: " .. config_file end local text = file:read("*a") file:close() return text end local function write_config_text(text) local file = io.open(config_file, "w") if not file then return false, "설정 파일에 쓸 수 없습니다: " .. config_file end file:write(text) file:close() return true end local function set_config_value(window, pane, key, value) local text, err = read_config_text() if not text then notify(window, err) return end local pattern = "(config%." .. key .. "%s*=%s*)[^\n]+" local updated, count = text:gsub(pattern, function(prefix) return prefix .. value end, 1) if count == 0 then notify(window, "설정 항목을 찾지 못했습니다: config." .. key) return end local ok, write_err = write_config_text(updated) if not ok then notify(window, write_err) return end notify(window, "저장됨: config." .. key .. " = " .. value) window:perform_action(act.ReloadConfiguration, pane) end local function prompt_config_value(window, pane, title, key, initial_value, quote_string) window:perform_action( act.PromptInputLine({ description = title .. " (현재: " .. tostring(initial_value) .. ")", action = wezterm.action_callback(function(cb_window, cb_pane, line) if not line or line == "" then return end local value = quote_string and string.format("%q", line) or line set_config_value(cb_window, cb_pane, key, value) end), }), pane ) end -- config.font = wezterm.font_with_fallback({ "영어 폰트", "한글 폰트" }) 블록에서 -- 현재 지정된 두 폰트 이름을 읽어온다 (설정 파일 텍스트 기준, 실행 중인 config 객체가 아님) local function get_current_fonts() local text = read_config_text() if not text then return nil, nil end local block = text:match("config%.font = wezterm%.font_with_fallback%(%{(.-)%}%)") if not block then return nil, nil end local fonts = {} for f in block:gmatch('"([^"]*)"') do table.insert(fonts, f) end return fonts[1], fonts[2] end local function set_fonts(window, pane, primary, korean) local text, err = read_config_text() if not text then notify(window, err) return end local new_block = string.format('\n\t%q, -- 영어/기본 폰트\n\t%q, -- 한글 폰트\n', primary, korean) local safe_block = new_block:gsub("%%", "%%%%") local updated, count = text:gsub("(config%.font = wezterm%.font_with_fallback%(%{).-(%}%))", "%1" .. safe_block .. "%2", 1) if count == 0 then notify(window, "config.font 설정을 찾지 못했습니다") return end local ok, write_err = write_config_text(updated) if not ok then notify(window, write_err) return end notify(window, "폰트 저장됨: 영어=" .. primary .. ", 한글=" .. korean) window:perform_action(act.ReloadConfiguration, pane) end local function prompt_font(window, pane, which) local primary, korean = get_current_fonts() primary = primary or "JetBrains Mono" korean = korean or "Noto Sans Mono CJK KR" local current = which == "primary" and primary or korean local title = (which == "primary" and "영어/기본 폰트" or "한글 폰트") .. " (현재: " .. current .. ")" window:perform_action( act.PromptInputLine({ description = title, action = wezterm.action_callback(function(cb_window, cb_pane, line) if not line or line == "" then return end if which == "primary" then set_fonts(cb_window, cb_pane, line, korean) else set_fonts(cb_window, cb_pane, primary, line) end end), }), pane ) end local function show_font_settings(window, pane) window:perform_action( act.InputSelector({ title = "폰트 설정", fuzzy = true, choices = { { label = "폰트 크기", id = "font_size" }, { label = "영어 폰트", id = "font_primary" }, { label = "한글 폰트", id = "font_korean" }, { label = "줄 높이", id = "line_height" }, { label = "색상 테마", id = "color_scheme" }, { label = "창 투명도", id = "opacity" }, }, action = wezterm.action_callback(function(inner_window, inner_pane, id) if id == "font_size" then prompt_config_value(inner_window, inner_pane, "폰트 크기", "font_size", config.font_size, false) elseif id == "font_primary" then prompt_font(inner_window, inner_pane, "primary") elseif id == "font_korean" then prompt_font(inner_window, inner_pane, "korean") elseif id == "line_height" then prompt_config_value(inner_window, inner_pane, "줄 높이", "line_height", config.line_height, false) elseif id == "color_scheme" then prompt_config_value(inner_window, inner_pane, "색상 테마", "color_scheme", config.color_scheme, true) elseif id == "opacity" then prompt_config_value(inner_window, inner_pane, "창 투명도", "window_background_opacity", config.window_background_opacity, false) end end), }), pane ) end local function escape_pattern(s) return (s:gsub("[%^%$%(%)%.%[%]%*%+%-%?%%]", "%%%1")) end -- "host:port" 형태의 remote_address를 host, port로 분리 (port 없으면 port는 nil) local function split_remote_address(remote_address) local host, port = remote_address:match("^(.-):(%d+)$") if host then return host, port end return remote_address, nil end local function append_ssh_domain(window, pane, name, remote_address, username) local text, err = read_config_text() if not text then notify(window, err) return end local entry = string.format( '\t{\n\t\tname = %q,\n\t\tremote_address = %q,\n\t\tusername = %q,\n\t\tmultiplexing = "None",\n\t},\n', name, remote_address, username ) local updated, count = text:gsub("\n}%s*\n\n%-%- Unix domain", "\n" .. entry .. "}\n\n-- Unix domain", 1) if count == 0 then notify(window, "config.ssh_domains 끝 위치를 찾지 못했습니다") return end local ok, write_err = write_config_text(updated) if not ok then notify(window, write_err) return end notify(window, "SSH 도메인 저장됨: " .. name .. " -> " .. remote_address .. " (WezTerm을 완전히 재시작해야 연결 목록에 나타납니다)") window:perform_action(act.ReloadConfiguration, pane) end local function update_ssh_domain(window, pane, old_name, name, remote_address, username) local text, err = read_config_text() if not text then notify(window, err) return end local quoted_old_name = escape_pattern(string.format("%q", old_name)) local pattern = "\t{\n\t\tname = " .. quoted_old_name .. ",\n.-\n\t},\n" local entry = string.format( '\t{\n\t\tname = %q,\n\t\tremote_address = %q,\n\t\tusername = %q,\n\t\tmultiplexing = "None",\n\t},\n', name, remote_address, username ) local safe_entry = entry:gsub("%%", "%%%%") local updated, count = text:gsub(pattern, safe_entry, 1) if count == 0 then notify(window, "SSH 도메인 항목을 찾지 못했습니다: " .. old_name) return end local ok, write_err = write_config_text(updated) if not ok then notify(window, write_err) return end notify(window, "SSH 도메인 수정됨: " .. name .. " -> " .. remote_address .. " (WezTerm을 완전히 재시작해야 반영됩니다)") window:perform_action(act.ReloadConfiguration, pane) end -- SSH 도메인 등록을 이름 -> 주소 -> 포트 -> 사용자명 순서로 한 칸씩 물어봄 -- (WezTerm에는 입력칸을 여러 개 한 화면에 배치하는 폼 위젯이 없어, 한 줄짜리 -- PromptInputLine을 단계별로 이어서 띄우는 방식으로 흉내낸다) local ssh_domain_steps = { { field = "name", title = "SSH 이름 (1/4, 예: prod)" }, { field = "host", title = "SSH 주소 (2/4, 예: myserver.example.com)" }, { field = "port", title = "SSH 포트 (3/4, 비워두면 기본값 22)", optional = true }, { field = "username", title = "SSH 사용자명 (4/4, 예: ubuntu)" }, } local function prompt_ssh_domain_step(window, pane, step_index, data) local step = ssh_domain_steps[step_index] window:perform_action( act.PromptInputLine({ description = step.title, action = wezterm.action_callback(function(cb_window, cb_pane, line) if (not line or line == "") and not step.optional then notify(cb_window, "SSH 도메인 추가를 취소했습니다") return end data[step.field] = line or "" if step_index < #ssh_domain_steps then prompt_ssh_domain_step(cb_window, cb_pane, step_index + 1, data) else local remote_address = data.host if data.port ~= "" then remote_address = remote_address .. ":" .. data.port end append_ssh_domain(cb_window, cb_pane, data.name, remote_address, data.username) end end), }), pane ) end local function prompt_ssh_domain(window, pane) prompt_ssh_domain_step(window, pane, 1, {}) end -- SSH 도메인 수정: 기존 값은 비워두면 그대로 유지, 입력하면 해당 항목만 교체 local edit_ssh_domain_steps = { { field = "name", label = "SSH 이름" }, { field = "host", label = "SSH 주소" }, { field = "port", label = "SSH 포트" }, { field = "username", label = "SSH 사용자명" }, } local function prompt_edit_ssh_domain_step(window, pane, step_index, data) local step = edit_ssh_domain_steps[step_index] local current_display = data[step.field] if step.field == "port" and current_display == "" then current_display = "기본값 22" end window:perform_action( act.PromptInputLine({ description = string.format( "%s (%d/%d, 현재: %s, 비워두면 유지)", step.label, step_index, #edit_ssh_domain_steps, current_display ), action = wezterm.action_callback(function(cb_window, cb_pane, line) if line and line ~= "" then data[step.field] = line end if step_index < #edit_ssh_domain_steps then prompt_edit_ssh_domain_step(cb_window, cb_pane, step_index + 1, data) else local remote_address = data.host if data.port ~= "" then remote_address = remote_address .. ":" .. data.port end update_ssh_domain(cb_window, cb_pane, data.original_name, data.name, remote_address, data.username) end end), }), pane ) end local function prompt_edit_ssh_domain(window, pane, domain) local host, port = split_remote_address(domain.remote_address) prompt_edit_ssh_domain_step(window, pane, 1, { original_name = domain.name, name = domain.name, host = host, port = port or "", username = domain.username, }) end local function show_edit_ssh_domain_list(window, pane) local choices = {} for _, domain in ipairs(config.ssh_domains or {}) do table.insert(choices, { label = domain.name .. " (" .. domain.remote_address .. ")", id = domain.name, }) end if #choices == 0 then notify(window, "등록된 SSH 도메인이 없습니다") return end window:perform_action( act.InputSelector({ title = "SSH 도메인 수정", fuzzy = true, choices = choices, action = wezterm.action_callback(function(inner_window, inner_pane, id) if not id then return end for _, domain in ipairs(config.ssh_domains or {}) do if domain.name == id then prompt_edit_ssh_domain(inner_window, inner_pane, domain) return end end end), }), pane ) end local function show_connection_settings(window, pane) window:perform_action( act.InputSelector({ title = "연결 설정", fuzzy = true, choices = { { label = "SSH 도메인 추가", id = "add_ssh" }, { label = "SSH 도메인 수정", id = "edit_ssh" }, { label = "SSH 도메인 목록", id = "domains" }, { label = "통합 런처", id = "launcher" }, }, action = wezterm.action_callback(function(inner_window, inner_pane, id) if id == "add_ssh" then prompt_ssh_domain(inner_window, inner_pane) elseif id == "edit_ssh" then show_edit_ssh_domain_list(inner_window, inner_pane) elseif id == "domains" then inner_window:perform_action(act.ShowLauncherArgs({ flags = "DOMAINS" }), inner_pane) elseif id == "launcher" then inner_window:perform_action(act.ShowLauncherArgs({ flags = "FUZZY|TABS|DOMAINS|WORKSPACES|LAUNCH_MENU_ITEMS" }), inner_pane) end end), }), pane ) end local function show_other_settings(window, pane) window:perform_action( act.InputSelector({ title = "기타 설정", fuzzy = true, choices = { { label = "스크롤백 줄 수", id = "scrollback" }, { label = "최대 FPS", id = "max_fps" }, { label = "탭바 위치: 상단", id = "tab_top" }, { label = "탭바 위치: 하단", id = "tab_bottom" }, { label = "창 닫기 확인 켜기", id = "close_prompt" }, { label = "창 닫기 확인 끄기", id = "close_no_prompt" }, }, action = wezterm.action_callback(function(inner_window, inner_pane, id) if id == "scrollback" then prompt_config_value(inner_window, inner_pane, "스크롤백 줄 수", "scrollback_lines", config.scrollback_lines, false) elseif id == "max_fps" then prompt_config_value(inner_window, inner_pane, "최대 FPS", "max_fps", config.max_fps, false) elseif id == "tab_top" then set_config_value(inner_window, inner_pane, "tab_bar_at_bottom", "false") elseif id == "tab_bottom" then set_config_value(inner_window, inner_pane, "tab_bar_at_bottom", "true") elseif id == "close_prompt" then set_config_value(inner_window, inner_pane, "window_close_confirmation", string.format("%q", "AlwaysPrompt")) elseif id == "close_no_prompt" then set_config_value(inner_window, inner_pane, "window_close_confirmation", string.format("%q", "NeverPrompt")) end end), }), pane ) end local function show_settings_menu(window, pane) window:perform_action( act.InputSelector({ title = "설정", fuzzy = true, choices = { { label = "폰트 설정", id = "font" }, { label = "연결 설정", id = "connection" }, { label = "기타 설정", id = "other" }, { label = "통합 런처", id = "launcher" }, { label = "설정 다시 불러오기", id = "reload" }, }, action = wezterm.action_callback(function(inner_window, inner_pane, id) if not id then return end if id == "font" then show_font_settings(inner_window, inner_pane) elseif id == "connection" then show_connection_settings(inner_window, inner_pane) elseif id == "other" then show_other_settings(inner_window, inner_pane) elseif id == "launcher" then inner_window:perform_action( act.ShowLauncherArgs({ flags = "FUZZY|TABS|DOMAINS|WORKSPACES|LAUNCH_MENU_ITEMS" }), inner_pane ) elseif id == "reload" then inner_window:perform_action(act.ReloadConfiguration, inner_pane) end end), }), pane ) end -- 메인 메뉴: 등록된 SSH 연결들을 맨 위에, 설정 항목은 맨 아래에 배치 local function show_main_menu(window, pane) local choices = {} for _, domain in ipairs(config.ssh_domains or {}) do table.insert(choices, { label = domain.name .. " (" .. domain.remote_address .. ")", id = "domain:" .. domain.name, }) end table.insert(choices, { label = "설정", id = "settings" }) window:perform_action( act.InputSelector({ title = "메인 메뉴", fuzzy = true, choices = choices, action = wezterm.action_callback(function(inner_window, inner_pane, id) if not id then return end local domain_name = id:match("^domain:(.+)$") if domain_name then inner_window:perform_action(act.SpawnTab({ DomainName = domain_name }), inner_pane) elseif id == "settings" then show_settings_menu(inner_window, inner_pane) end end), }), pane ) end wezterm.on("show-main-menu", function(window, pane) show_main_menu(window, pane) end) -- 시작 시 첫 화면으로 메인 메뉴를 띄워 설정과 연결 메뉴에 바로 접근합니다. wezterm.on("gui-startup", function(cmd) local tab, pane, window = wezterm.mux.spawn_window(cmd or {}) show_main_menu(window:gui_window(), pane) end) -- 시작 시 자동으로 워크스페이스/레이아웃을 구성하려면 아래 예시를 참고하세요. -- wezterm.on("gui-startup", function(cmd) -- local tab, pane, window = wezterm.mux.spawn_window(cmd or {}) -- pane:split({ direction = "Right", size = 0.3 }) -- end) -- 워크스페이스 전환 (tmux 세션 개념) : leader+w 로 목록에서 선택/생성 -- (아래 keys 테이블에 SwitchToWorkspace, ShowLauncherArgs 바인딩 있음) -- ============================================================ -- 키바인딩 (Leader key: tmux 스타일) -- ============================================================ config.leader = { key = "a", mods = "CTRL", timeout_milliseconds = 1000 } config.keys = { -- 탭 (Windows 관례: Ctrl+Shift+*, 셸 시그널/기존 단축키와 충돌 피함) { key = "t", mods = "CTRL|SHIFT", action = act.SpawnTab("CurrentPaneDomain") }, { key = "w", mods = "CTRL|SHIFT", action = act.CloseCurrentTab({ confirm = true }) }, { key = "1", mods = "CTRL|SHIFT", action = act.ActivateTab(0) }, { key = "2", mods = "CTRL|SHIFT", action = act.ActivateTab(1) }, { key = "3", mods = "CTRL|SHIFT", action = act.ActivateTab(2) }, -- 창 분할 (leader + | / -) { key = "\\", mods = "LEADER", action = act.SplitHorizontal({ domain = "CurrentPaneDomain" }) }, { key = "-", mods = "LEADER", action = act.SplitVertical({ domain = "CurrentPaneDomain" }) }, -- 패인(pane) 이동: leader + hjkl { key = "h", mods = "LEADER", action = act.ActivatePaneDirection("Left") }, { key = "l", mods = "LEADER", action = act.ActivatePaneDirection("Right") }, { key = "k", mods = "LEADER", action = act.ActivatePaneDirection("Up") }, { key = "j", mods = "LEADER", action = act.ActivatePaneDirection("Down") }, -- 패인 닫기 / 전체화면 토글 { key = "x", mods = "LEADER", action = act.CloseCurrentPane({ confirm = true }) }, { key = "z", mods = "LEADER", action = act.TogglePaneZoomState }, -- 폰트 크기 조절 (브라우저와 동일한 관례: Ctrl+=/-/0) { key = "=", mods = "CTRL", action = act.IncreaseFontSize }, { key = "-", mods = "CTRL", action = act.DecreaseFontSize }, { key = "0", mods = "CTRL", action = act.ResetFontSize }, -- 복사/붙여넣기 (Ctrl+C/V는 셸 시그널/붙여넣기와 겹치므로 Shift 추가) { key = "c", mods = "CTRL|SHIFT", action = act.CopyTo("Clipboard") }, { key = "v", mods = "CTRL|SHIFT", action = act.PasteFrom("Clipboard") }, -- 메인 메뉴: 설정 메뉴와 통합 런처를 다시 표시 { key = "m", mods = "LEADER", action = act.EmitEvent("show-main-menu") }, -- [2] 워크스페이스(=tmux 세션) 전환/생성 목록 { key = "w", mods = "LEADER", action = act.ShowLauncherArgs({ flags = "WORKSPACES" }) }, -- [2] SSH 도메인 접속 (config.ssh_domains에 등록한 서버 목록에서 선택) { key = "s", mods = "LEADER", action = act.ShowLauncherArgs({ flags = "DOMAINS" }) }, -- [MobaXterm 스타일] 저장된 세션 바로가기 목록 (config.launch_menu, 퍼지 검색 가능) { key = "e", mods = "LEADER", action = act.ShowLauncherArgs({ flags = "FUZZY|LAUNCH_MENU_ITEMS" }) }, -- [MobaXterm 스타일] 탭/도메인/워크스페이스/launch_menu 전부 합친 통합 런처 { key = "Tab", mods = "LEADER", action = act.ShowLauncherArgs({ flags = "FUZZY|TABS|DOMAINS|WORKSPACES|LAUNCH_MENU_ITEMS" }) }, -- [3] 검색 모드: leader+/ 로 스크롤백 문자열 검색 { key = "/", mods = "LEADER", action = act.Search({ CaseInSensitiveString = "" }) }, -- [3] Copy mode: leader+[ 로 진입, vim 키(hjkl/v/y)로 스크롤백 탐색 & 선택 { key = "[", mods = "LEADER", action = act.ActivateCopyMode }, -- [3] Quick select: leader+space 로 화면에 보이는 URL/해시/경로 등을 정규식으로 잡아 즉시 복사 { key = "Space", mods = "LEADER", action = act.QuickSelect }, -- [6] Resize 모드 진입: leader+r 누른 뒤 모드 안에서 hjkl로 계속 크기 조절, Esc로 빠져나감 { key = "r", mods = "LEADER", action = act.ActivateKeyTable({ name = "resize_pane", one_shot = false }) }, } -- [6] key_tables: resize 모드처럼 진입 후 여러 번 연속 입력 가능한 모달 키바인딩 config.key_tables = { resize_pane = { { key = "h", action = act.AdjustPaneSize({ "Left", 3 }) }, { key = "l", action = act.AdjustPaneSize({ "Right", 3 }) }, { key = "k", action = act.AdjustPaneSize({ "Up", 3 }) }, { key = "j", action = act.AdjustPaneSize({ "Down", 3 }) }, { key = "Escape", action = "PopKeyTable" }, { key = "q", action = "PopKeyTable" }, }, } -- [6] 마우스 바인딩: Ctrl+Shift+우클릭으로 붙여넣기, Ctrl+클릭으로 링크 열기 config.mouse_bindings = { { event = { Down = { streak = 1, button = "Right" } }, mods = "CTRL|SHIFT", action = act.PasteFrom("Clipboard"), }, { event = { Up = { streak = 1, button = "Left" } }, mods = "ALT", action = act.EmitEvent("show-main-menu"), }, { event = { Up = { streak = 1, button = "Left" } }, mods = "CTRL", action = act.OpenLinkAtMouseCursor, }, } -- [3] Quick select 패턴 추가 (기본 URL/git hash 외에 IP주소, K8s pod명 등 추가 가능) config.quick_select_patterns = { "\\b(?:25[0-5]|2[0-4][0-9]|1?[0-9]?[0-9])\\.(?:25[0-5]|2[0-4][0-9]|1?[0-9]?[0-9])\\.(?:25[0-5]|2[0-4][0-9]|1?[0-9]?[0-9])\\.(?:25[0-5]|2[0-4][0-9]|1?[0-9]?[0-9])\\b", -- IPv4 } -- ============================================================ -- [4] 하이퍼링크 / 이미지 -- ============================================================ -- 기본 URL 규칙 + 커스텀 규칙 추가 config.hyperlink_rules = wezterm.default_hyperlink_rules() -- JIRA-1234 같은 티켓 링크가 필요하면 실제 도메인으로 바꾼 뒤 활성화하세요. -- table.insert(config.hyperlink_rules, { -- regex = [[\b[A-Z]{2,}-\d+\b]], -- format = "https://jira.example.com/browse/$0", -- }) -- Kitty graphics / iTerm2 / Sixel 이미지 프로토콜 활성화 (chafa, catimg 등으로 터미널 이미지 출력 가능) config.enable_kitty_graphics = true -- ============================================================ -- [5] 쉘 연동 -- ============================================================ -- 기본 실행 쉘/프로그램 (미지정 시 OS 기본 쉘) -- config.default_prog = { "/bin/zsh", "-l" } -- OSC 7으로 현재 디렉토리를 추적하려면 쉘 설정(.zshrc/.bashrc)에 shell integration 스크립트 추가 필요: -- zsh: eval "$(wezterm shell-integration --shell zsh)" (또는 wezterm 문서의 zshrc 스니펫) -- 적용되면 새 탭/분할이 현재 pane과 같은 디렉토리에서 시작됨 config.default_cwd = wezterm.home_dir -- [MobaXterm 스타일] 저장된 세션 목록: leader+e (또는 우클릭 메뉴)로 목록을 띄워 -- 원하는 서버/커맨드를 바로 골라 새 탭으로 실행. 항목은 필요한 만큼 추가하면 됨. -- (아래는 예시이며 실제 서버 정보로 교체 필요) config.launch_menu = { { label = "Local bash", args = { "bash", "-l" } }, { label = "htop", args = { "htop" } }, -- { label = "SSH: production", args = { "ssh", "user@production.example.com" } }, -- { label = "SSH: devbox", args = { "ssh", "user@devbox.example.com" } }, -- { label = "SSH: root custom port", args = { "ssh", "-p", "2222", "root@host.example.com" } }, } -- ============================================================ -- [7] 렌더링 / 성능 -- ============================================================ config.front_end = "WebGpu" -- GPU 가속 렌더러 (문제 있으면 "OpenGL" 또는 "Software"로 변경) config.webgpu_power_preference = "HighPerformance" config.max_fps = 120 config.animation_fps = 60 -- 리가처(ligature) 비활성화 (=> 같은 코드 기호가 하나로 합쳐지는 게 싫으면 끄기) config.harfbuzz_features = { "calt=0", "clig=0", "liga=0" } -- ============================================================ -- 기타 -- ============================================================ config.scrollback_lines = 10000 config.enable_scroll_bar = false config.audible_bell = "Disabled" config.default_cursor_style = "BlinkingBar" config.window_close_confirmation = "AlwaysPrompt" return config