232 lines
10 KiB
Swift
232 lines
10 KiB
Swift
import Foundation
|
|
import MacToolsPluginKit
|
|
|
|
// MARK: - ZshConfigFileType
|
|
|
|
/// Standard zsh configuration files in the user's home directory.
|
|
enum ZshConfigFileType: String, CaseIterable, Identifiable, Codable {
|
|
case zshrc = ".zshrc"
|
|
case zshenv = ".zshenv"
|
|
case zprofile = ".zprofile"
|
|
case zlogin = ".zlogin"
|
|
case zlogout = ".zlogout"
|
|
|
|
var id: String { rawValue }
|
|
var filename: String { rawValue }
|
|
|
|
/// One-sentence description of the file's role.
|
|
var role: String {
|
|
role()
|
|
}
|
|
|
|
func role(localization: PluginLocalization = PluginLocalization(bundle: .main)) -> String {
|
|
switch self {
|
|
case .zshrc:
|
|
return localization.string("file.zshrc.role", defaultValue: "交互式 Shell 配置:别名、函数、提示符等")
|
|
case .zshenv:
|
|
return localization.string("file.zshenv.role", defaultValue: "所有 Shell 的环境变量,最先被加载")
|
|
case .zprofile:
|
|
return localization.string("file.zprofile.role", defaultValue: "登录 Shell 初始化,早于 .zshrc")
|
|
case .zlogin:
|
|
return localization.string("file.zlogin.role", defaultValue: "登录 Shell 完成初始化后执行")
|
|
case .zlogout:
|
|
return localization.string("file.zlogout.role", defaultValue: "退出登录 Shell 时执行的清理脚本")
|
|
}
|
|
}
|
|
|
|
/// Describes when zsh loads this file.
|
|
var whenLoaded: String {
|
|
whenLoaded()
|
|
}
|
|
|
|
func whenLoaded(localization: PluginLocalization = PluginLocalization(bundle: .main)) -> String {
|
|
switch self {
|
|
case .zshrc:
|
|
return localization.string("file.zshrc.whenLoaded", defaultValue: "每次打开新终端窗口/标签页时")
|
|
case .zshenv:
|
|
return localization.string("file.zshenv.whenLoaded", defaultValue: "每次启动 zsh(包括脚本、非交互式)时")
|
|
case .zprofile:
|
|
return localization.string("file.zprofile.whenLoaded", defaultValue: "登录时(SSH、macOS 登录等),早于 .zshrc")
|
|
case .zlogin:
|
|
return localization.string("file.zlogin.whenLoaded", defaultValue: "登录时,晚于 .zshrc")
|
|
case .zlogout:
|
|
return localization.string("file.zlogout.whenLoaded", defaultValue: "登录 Shell 退出时(`exit` 或关闭终端)")
|
|
}
|
|
}
|
|
|
|
/// Recommended uses for this file.
|
|
var recommendedUse: String {
|
|
recommendedUse()
|
|
}
|
|
|
|
func recommendedUse(localization: PluginLocalization = PluginLocalization(bundle: .main)) -> String {
|
|
switch self {
|
|
case .zshrc:
|
|
return localization.string("file.zshrc.recommendedUse", defaultValue: "alias、函数、主题(Oh My Zsh / Starship 等)、PATH")
|
|
case .zshenv:
|
|
return localization.string("file.zshenv.recommendedUse", defaultValue: "EDITOR、LANG、必须对所有子进程可见的变量")
|
|
case .zprofile:
|
|
return localization.string("file.zprofile.recommendedUse", defaultValue: "Homebrew 的环境初始化、一次性登录脚本")
|
|
case .zlogin:
|
|
return localization.string("file.zlogin.recommendedUse", defaultValue: "欢迎信息、tmux 自动启动等")
|
|
case .zlogout:
|
|
return localization.string("file.zlogout.recommendedUse", defaultValue: "清理临时文件、记录会话日志")
|
|
}
|
|
}
|
|
|
|
/// Absolute URL under the user's home directory.
|
|
var fileURL: URL {
|
|
FileManager.default.homeDirectoryForCurrentUser
|
|
.appendingPathComponent(rawValue)
|
|
}
|
|
}
|
|
|
|
// MARK: - ZshFileStatus
|
|
|
|
/// Current status snapshot for a zsh configuration file.
|
|
struct ZshFileStatus {
|
|
let type: ZshConfigFileType
|
|
let exists: Bool
|
|
let isWritable: Bool
|
|
let byteSize: Int
|
|
let modifiedDate: Date?
|
|
|
|
var formattedSize: String {
|
|
guard exists else { return "" }
|
|
if byteSize < 1024 { return "\(byteSize) B" }
|
|
return String(format: "%.1f KB", Double(byteSize) / 1024)
|
|
}
|
|
|
|
/// Probes the actual file status through FileManager.
|
|
static func probe(_ type: ZshConfigFileType) -> ZshFileStatus {
|
|
let url = type.fileURL
|
|
let fm = FileManager.default
|
|
var isDir: ObjCBool = false
|
|
let exists = fm.fileExists(atPath: url.path, isDirectory: &isDir) && !isDir.boolValue
|
|
let isWritable: Bool
|
|
if exists {
|
|
isWritable = fm.isWritableFile(atPath: url.path)
|
|
} else {
|
|
isWritable = fm.isWritableFile(atPath: url.deletingLastPathComponent().path)
|
|
}
|
|
var byteSize = 0
|
|
var modifiedDate: Date? = nil
|
|
if exists,
|
|
let attrs = try? fm.attributesOfItem(atPath: url.path) {
|
|
byteSize = (attrs[.size] as? Int) ?? 0
|
|
modifiedDate = attrs[.modificationDate] as? Date
|
|
}
|
|
return ZshFileStatus(
|
|
type: type,
|
|
exists: exists,
|
|
isWritable: isWritable,
|
|
byteSize: byteSize,
|
|
modifiedDate: modifiedDate
|
|
)
|
|
}
|
|
}
|
|
|
|
// MARK: - ZshSnippet
|
|
|
|
/// Quick-insert snippet template.
|
|
struct ZshSnippet: Identifiable, Sendable {
|
|
let id: String
|
|
let icon: String
|
|
let title: String
|
|
let description: String
|
|
let placeholder: String
|
|
/// Builds the text to insert from user input.
|
|
let buildContent: @Sendable (String) -> String
|
|
|
|
static let all: [ZshSnippet] = localizedSnippets()
|
|
|
|
static func localizedSnippets(
|
|
localization: PluginLocalization = PluginLocalization(bundle: .main)
|
|
) -> [ZshSnippet] {
|
|
[
|
|
ZshSnippet(
|
|
id: "alias",
|
|
icon: "arrow.uturn.right",
|
|
title: localization.string("snippet.alias.title", defaultValue: "别名"),
|
|
description: localization.string("snippet.alias.description", defaultValue: "为命令创建快捷方式"),
|
|
placeholder: localization.string("snippet.alias.placeholder", defaultValue: "名称=命令,例:gs=git status"),
|
|
buildContent: { input in
|
|
let trimmed = input.trimmingCharacters(in: .whitespaces)
|
|
let parts = trimmed.split(separator: "=", maxSplits: 1).map(String.init)
|
|
if parts.count == 2 {
|
|
return "alias \(parts[0].trimmingCharacters(in: .whitespaces))='\(parts[1].trimmingCharacters(in: .whitespaces))'"
|
|
}
|
|
return "alias \(trimmed)=''"
|
|
}
|
|
),
|
|
ZshSnippet(
|
|
id: "export",
|
|
icon: "square.and.arrow.up",
|
|
title: localization.string("snippet.export.title", defaultValue: "环境变量"),
|
|
description: localization.string("snippet.export.description", defaultValue: "设置或覆盖环境变量"),
|
|
placeholder: localization.string("snippet.export.placeholder", defaultValue: "变量名=值,例:EDITOR=nvim"),
|
|
buildContent: { input in
|
|
let trimmed = input.trimmingCharacters(in: .whitespaces)
|
|
let parts = trimmed.split(separator: "=", maxSplits: 1).map(String.init)
|
|
if parts.count == 2 {
|
|
return "export \(parts[0].trimmingCharacters(in: .whitespaces))=\(parts[1].trimmingCharacters(in: .whitespaces))"
|
|
}
|
|
return "export \(trimmed)="
|
|
}
|
|
),
|
|
ZshSnippet(
|
|
id: "path",
|
|
icon: "folder.badge.plus",
|
|
title: localization.string("snippet.path.title", defaultValue: "PATH 路径"),
|
|
description: localization.string("snippet.path.description", defaultValue: "将目录追加到 $PATH"),
|
|
placeholder: localization.string("snippet.path.placeholder", defaultValue: "目录路径,例:/opt/homebrew/bin"),
|
|
buildContent: { input in
|
|
let path = input.trimmingCharacters(in: .whitespaces)
|
|
guard !path.isEmpty else { return "export PATH=\"$PATH:/your/path\"" }
|
|
return "export PATH=\"$PATH:\(path)\""
|
|
}
|
|
),
|
|
ZshSnippet(
|
|
id: "source",
|
|
icon: "doc.badge.plus",
|
|
title: localization.string("snippet.source.title", defaultValue: "加载文件"),
|
|
description: localization.string("snippet.source.description", defaultValue: "在当前 Shell 中执行另一个脚本"),
|
|
placeholder: localization.string("snippet.source.placeholder", defaultValue: "文件路径,例:~/.config/secrets.sh"),
|
|
buildContent: { input in
|
|
let path = input.trimmingCharacters(in: .whitespaces)
|
|
guard !path.isEmpty else { return "source ~/.your-script.sh" }
|
|
return "source \(path)"
|
|
}
|
|
),
|
|
ZshSnippet(
|
|
id: "function",
|
|
icon: "f.cursive",
|
|
title: localization.string("snippet.function.title", defaultValue: "函数"),
|
|
description: localization.string("snippet.function.description", defaultValue: "定义一个可复用的 Shell 函数"),
|
|
placeholder: localization.string("snippet.function.placeholder", defaultValue: "函数名称,例:mkcd"),
|
|
buildContent: { input in
|
|
let name = input.trimmingCharacters(in: .whitespaces)
|
|
let funcName = name.isEmpty ? "my_func" : name
|
|
let comment = localization.string("snippet.function.bodyComment", defaultValue: "在此填写函数内容")
|
|
return "\(funcName)() {\n # \(comment)\n \n}"
|
|
}
|
|
),
|
|
ZshSnippet(
|
|
id: "eval",
|
|
icon: "terminal",
|
|
title: localization.string("snippet.eval.title", defaultValue: "初始化工具"),
|
|
description: localization.string("snippet.eval.description", defaultValue: "执行工具的 eval 初始化命令"),
|
|
placeholder: localization.string("snippet.eval.placeholder", defaultValue: "工具命令,例:$(brew shellenv)"),
|
|
buildContent: { input in
|
|
let cmd = input.trimmingCharacters(in: .whitespaces)
|
|
guard !cmd.isEmpty else { return "eval \"$(your-tool init zsh)\"" }
|
|
if cmd.hasPrefix("$(") || cmd.hasPrefix("`") {
|
|
return "eval \"\(cmd)\""
|
|
}
|
|
return "eval \"$(\(cmd))\""
|
|
}
|
|
),
|
|
]
|
|
}
|
|
}
|