autosnippet 3.2.21 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/dashboard/dist/assets/{icons-C1dUryS-.js → icons-BofcEZ3f.js} +1 -1
  2. package/dashboard/dist/assets/index-SiN1GChm.js +128 -0
  3. package/dashboard/dist/index.html +2 -2
  4. package/dist/bin/cli.d.ts +0 -1
  5. package/dist/bin/cli.js +0 -133
  6. package/dist/lib/cli/SetupService.d.ts +46 -2
  7. package/dist/lib/cli/SetupService.js +2 -27
  8. package/dist/lib/{platform/ios/spm → core/discovery}/SpmDiscoverer.d.ts +2 -5
  9. package/dist/lib/{platform/ios/spm → core/discovery}/SpmDiscoverer.js +159 -44
  10. package/dist/lib/core/discovery/index.d.ts +1 -1
  11. package/dist/lib/core/discovery/index.js +2 -2
  12. package/dist/lib/external/mcp/handlers/guard.js +6 -3
  13. package/dist/lib/http/HttpServer.js +0 -6
  14. package/dist/lib/http/routes/commands.d.ts +1 -1
  15. package/dist/lib/http/routes/commands.js +1 -66
  16. package/dist/lib/http/routes/remote.js +0 -5
  17. package/dist/lib/injection/ServiceMap.d.ts +0 -9
  18. package/dist/lib/injection/modules/AppModule.d.ts +2 -3
  19. package/dist/lib/injection/modules/AppModule.js +3 -30
  20. package/dist/lib/injection/modules/GuardModule.js +33 -1
  21. package/dist/lib/service/guard/GuardCheckEngine.d.ts +13 -1
  22. package/dist/lib/service/guard/GuardCheckEngine.js +44 -2
  23. package/dist/lib/service/module/ModuleService.js +3 -13
  24. package/dist/lib/service/search/SearchEngine.js +1 -1
  25. package/dist/lib/shared/constants.d.ts +0 -15
  26. package/dist/lib/shared/constants.js +0 -10
  27. package/dist/lib/shared/schemas/config.d.ts +4 -1
  28. package/dist/lib/shared/schemas/config.js +8 -1
  29. package/dist/scripts/release.js +2 -10
  30. package/package.json +4 -19
  31. package/dashboard/dist/assets/index-DdvZE4Yd.js +0 -128
  32. package/dist/lib/http/routes/snippets.d.ts +0 -6
  33. package/dist/lib/http/routes/snippets.js +0 -49
  34. package/dist/lib/platform/ClipboardManager.d.ts +0 -24
  35. package/dist/lib/platform/ClipboardManager.js +0 -142
  36. package/dist/lib/platform/NativeUi.d.ts +0 -53
  37. package/dist/lib/platform/NativeUi.js +0 -284
  38. package/dist/lib/platform/ios/index.d.ts +0 -38
  39. package/dist/lib/platform/ios/index.js +0 -42
  40. package/dist/lib/platform/ios/routes/spm.d.ts +0 -9
  41. package/dist/lib/platform/ios/routes/spm.js +0 -371
  42. package/dist/lib/platform/ios/snippet/PlaceholderConverter.d.ts +0 -21
  43. package/dist/lib/platform/ios/snippet/PlaceholderConverter.js +0 -48
  44. package/dist/lib/platform/ios/snippet/XcodeCodec.d.ts +0 -23
  45. package/dist/lib/platform/ios/snippet/XcodeCodec.js +0 -96
  46. package/dist/lib/platform/ios/spm/DependencyGraph.d.ts +0 -56
  47. package/dist/lib/platform/ios/spm/DependencyGraph.js +0 -195
  48. package/dist/lib/platform/ios/spm/PackageSwiftParser.d.ts +0 -69
  49. package/dist/lib/platform/ios/spm/PackageSwiftParser.js +0 -231
  50. package/dist/lib/platform/ios/spm/PathFinder.d.ts +0 -28
  51. package/dist/lib/platform/ios/spm/PathFinder.js +0 -117
  52. package/dist/lib/platform/ios/spm/PolicyEngine.d.ts +0 -44
  53. package/dist/lib/platform/ios/spm/PolicyEngine.js +0 -79
  54. package/dist/lib/platform/ios/spm/SpmHelper.d.ts +0 -102
  55. package/dist/lib/platform/ios/spm/SpmHelper.js +0 -464
  56. package/dist/lib/platform/ios/xcode/HeaderResolver.d.ts +0 -33
  57. package/dist/lib/platform/ios/xcode/HeaderResolver.js +0 -90
  58. package/dist/lib/platform/ios/xcode/SaveEventFilter.d.ts +0 -66
  59. package/dist/lib/platform/ios/xcode/SaveEventFilter.js +0 -142
  60. package/dist/lib/platform/ios/xcode/XcodeAutomation.d.ts +0 -71
  61. package/dist/lib/platform/ios/xcode/XcodeAutomation.js +0 -327
  62. package/dist/lib/platform/ios/xcode/XcodeImportResolver.d.ts +0 -130
  63. package/dist/lib/platform/ios/xcode/XcodeImportResolver.js +0 -404
  64. package/dist/lib/platform/ios/xcode/XcodeIntegration.d.ts +0 -89
  65. package/dist/lib/platform/ios/xcode/XcodeIntegration.js +0 -588
  66. package/dist/lib/platform/ios/xcode/XcodeWriteUtils.d.ts +0 -99
  67. package/dist/lib/platform/ios/xcode/XcodeWriteUtils.js +0 -190
  68. package/dist/lib/service/automation/ActionPipeline.d.ts +0 -34
  69. package/dist/lib/service/automation/ActionPipeline.js +0 -53
  70. package/dist/lib/service/automation/AutomationOrchestrator.d.ts +0 -86
  71. package/dist/lib/service/automation/AutomationOrchestrator.js +0 -57
  72. package/dist/lib/service/automation/ContextCollector.d.ts +0 -24
  73. package/dist/lib/service/automation/ContextCollector.js +0 -35
  74. package/dist/lib/service/automation/DirectiveDetector.d.ts +0 -51
  75. package/dist/lib/service/automation/DirectiveDetector.js +0 -112
  76. package/dist/lib/service/automation/FileWatcher.d.ts +0 -51
  77. package/dist/lib/service/automation/FileWatcher.js +0 -366
  78. package/dist/lib/service/automation/TriggerResolver.d.ts +0 -36
  79. package/dist/lib/service/automation/TriggerResolver.js +0 -62
  80. package/dist/lib/service/automation/handlers/AlinkHandler.d.ts +0 -7
  81. package/dist/lib/service/automation/handlers/AlinkHandler.js +0 -80
  82. package/dist/lib/service/automation/handlers/CreateHandler.d.ts +0 -11
  83. package/dist/lib/service/automation/handlers/CreateHandler.js +0 -170
  84. package/dist/lib/service/automation/handlers/GuardHandler.d.ts +0 -17
  85. package/dist/lib/service/automation/handlers/GuardHandler.js +0 -218
  86. package/dist/lib/service/automation/handlers/HeaderHandler.d.ts +0 -2
  87. package/dist/lib/service/automation/handlers/HeaderHandler.js +0 -32
  88. package/dist/lib/service/automation/handlers/SearchHandler.d.ts +0 -11
  89. package/dist/lib/service/automation/handlers/SearchHandler.js +0 -278
  90. package/dist/lib/service/snippet/SnippetFactory.d.ts +0 -101
  91. package/dist/lib/service/snippet/SnippetFactory.js +0 -145
  92. package/dist/lib/service/snippet/SnippetInstaller.d.ts +0 -91
  93. package/dist/lib/service/snippet/SnippetInstaller.js +0 -276
  94. package/dist/lib/service/snippet/codecs/SnippetCodec.d.ts +0 -44
  95. package/dist/lib/service/snippet/codecs/SnippetCodec.js +0 -35
  96. package/dist/lib/service/snippet/codecs/VSCodeCodec.d.ts +0 -27
  97. package/dist/lib/service/snippet/codecs/VSCodeCodec.js +0 -82
  98. package/dist/scripts/build-native-ui.d.ts +0 -3
  99. package/dist/scripts/build-native-ui.js +0 -62
  100. package/dist/scripts/init-snippets.d.ts +0 -30
  101. package/dist/scripts/init-snippets.js +0 -298
  102. package/dist/scripts/install-full.d.ts +0 -7
  103. package/dist/scripts/install-full.js +0 -38
  104. package/resources/native-ui/README.md +0 -29
  105. package/resources/native-ui/combined-window.swift +0 -494
  106. package/resources/native-ui/main.swift +0 -598
  107. package/scripts/postinstall-safe.mjs +0 -89
@@ -1,494 +0,0 @@
1
- import AppKit
2
- import Foundation
3
-
4
- /**
5
- * AutoSnippet 组合窗口 - 列表 + 预览一体化
6
- * 左侧:搜索结果列表
7
- * 右侧:代码预览(实时更新)
8
- * 底部:操作按钮
9
- */
10
-
11
- // MARK: - 语法高亮
12
-
13
- struct SyntaxHighlighter {
14
- static let keywords = ["func", "var", "let", "if", "else", "guard", "return", "import", "class", "struct", "enum", "protocol", "extension", "private", "public", "internal", "fileprivate", "static", "override", "init", "deinit", "self", "super", "nil", "true", "false", "for", "while", "repeat", "switch", "case", "default", "break", "continue", "fallthrough", "where", "in", "throws", "throw", "try", "catch", "async", "await", "typealias", "associatedtype", "weak", "unowned", "lazy", "final", "required", "convenience", "mutating", "nonmutating", "open", "inout", "some", "any",
15
- // Objective-C
16
- "@interface", "@implementation", "@end", "@property", "@synthesize", "@dynamic", "@class", "@protocol", "@optional", "@required", "@public", "@private", "@protected", "@package", "@selector", "@encode", "@synchronized", "@autoreleasepool", "@try", "@catch", "@finally", "@throw", "YES", "NO", "NULL", "nil", "self", "super", "id", "Class", "SEL", "IMP", "BOOL", "instancetype", "void", "char", "short", "int", "long", "float", "double", "signed", "unsigned", "const", "static", "extern", "auto", "register", "volatile", "inline", "restrict", "typedef", "sizeof", "typeof", "return", "if", "else", "for", "while", "do", "switch", "case", "default", "break", "continue", "goto", "struct", "union", "enum",
17
- // TypeScript/JavaScript
18
- "function", "const", "async", "await", "new", "this", "typeof", "instanceof", "export", "import", "from", "as", "default", "extends", "implements", "interface", "type", "namespace", "module", "declare", "abstract", "readonly", "keyof", "infer", "never", "unknown", "any", "void", "null", "undefined", "number", "string", "boolean", "symbol", "bigint", "object"
19
- ]
20
-
21
- static let typeKeywords = ["String", "Int", "Double", "Float", "Bool", "Array", "Dictionary", "Set", "Optional", "Any", "AnyObject", "Void", "Never", "Error", "Result", "URL", "Data", "Date", "UUID", "NSObject", "NSString", "NSNumber", "NSArray", "NSDictionary", "NSSet", "NSData", "NSDate", "NSURL", "CGFloat", "CGPoint", "CGSize", "CGRect", "UIView", "UIViewController", "UIButton", "UILabel", "UIImage", "UIColor", "NSView", "NSViewController", "NSButton", "NSTextField", "NSImage", "NSColor", "Promise", "Observable", "Subject"]
22
-
23
- static func highlight(_ code: String) -> NSAttributedString {
24
- let attributed = NSMutableAttributedString(string: code)
25
- let fullRange = NSRange(location: 0, length: code.utf16.count)
26
-
27
- // 基础样式 - SF Mono 字体 (Xcode 默认) - 增大到 14
28
- let baseFont = NSFont(name: "SFMono-Regular", size: 14) ?? NSFont.monospacedSystemFont(ofSize: 14, weight: .regular)
29
- // 普通文本 - Xcode 默认浅灰白色
30
- let baseColor = NSColor(calibratedRed: 0.83, green: 0.84, blue: 0.85, alpha: 1.0) // #D4D4D6
31
- attributed.addAttribute(.font, value: baseFont, range: fullRange)
32
- attributed.addAttribute(.foregroundColor, value: baseColor, range: fullRange)
33
-
34
- // 高亮注释 - Xcode 默认绿色 (系统 Green)
35
- let commentColor = NSColor(calibratedRed: 0.42, green: 0.75, blue: 0.31, alpha: 1.0) // #6BBF4F
36
- highlightPattern(attributed, pattern: "//.*$", color: commentColor, options: .anchorsMatchLines)
37
- highlightPattern(attributed, pattern: "/\\*[\\s\\S]*?\\*/", color: commentColor)
38
-
39
- // 高亮字符串 - Xcode 默认橙红色 (系统 Red/Orange 混合)
40
- let stringColor = NSColor(calibratedRed: 0.98, green: 0.42, blue: 0.33, alpha: 1.0) // #FA6B54
41
- highlightPattern(attributed, pattern: "\"(?:[^\"\\\\]|\\\\.)*\"", color: stringColor)
42
- highlightPattern(attributed, pattern: "'(?:[^'\\\\]|\\\\.)*'", color: stringColor)
43
- highlightPattern(attributed, pattern: "@\"(?:[^\"\\\\]|\\\\.)*\"", color: stringColor) // ObjC string
44
-
45
- // 高亮数字 - Xcode 默认紫色 (Light Purple)
46
- let numberColor = NSColor(calibratedRed: 0.69, green: 0.54, blue: 0.89, alpha: 1.0) // #B08AE3
47
- highlightPattern(attributed, pattern: "\\b\\d+\\.?\\d*\\b", color: numberColor)
48
-
49
- // 高亮类型关键字 - Xcode 默认青绿色 (Teal/Cyan)
50
- let typeColor = NSColor(calibratedRed: 0.40, green: 0.84, blue: 0.89, alpha: 1.0) // #66D7E3
51
- for keyword in typeKeywords {
52
- highlightWord(attributed, word: keyword, color: typeColor)
53
- }
54
-
55
- // 高亮关键字 - Xcode 默认粉紫色 (Magenta/Pink)
56
- let keywordColor = NSColor(calibratedRed: 0.98, green: 0.42, blue: 0.69, alpha: 1.0) // #FA6BB0
57
- // Objective-C @ 前缀关键字 - 使用浅棕色
58
- let atKeywordColor = NSColor(calibratedRed: 0.83, green: 0.60, blue: 0.45, alpha: 1.0) // #D49973
59
- for keyword in keywords {
60
- if keyword.hasPrefix("@") {
61
- highlightPattern(attributed, pattern: "\(keyword)\\b", color: atKeywordColor)
62
- } else {
63
- highlightWord(attributed, word: keyword, color: keywordColor)
64
- }
65
- }
66
-
67
- return attributed
68
- }
69
-
70
- private static func highlightPattern(_ attributed: NSMutableAttributedString, pattern: String, color: NSColor, options: NSRegularExpression.Options = []) {
71
- guard let regex = try? NSRegularExpression(pattern: pattern, options: options) else { return }
72
- let string = attributed.string
73
- let range = NSRange(location: 0, length: string.utf16.count)
74
-
75
- for match in regex.matches(in: string, options: [], range: range) {
76
- attributed.addAttribute(.foregroundColor, value: color, range: match.range)
77
- }
78
- }
79
-
80
- private static func highlightWord(_ attributed: NSMutableAttributedString, word: String, color: NSColor) {
81
- let pattern = "\\b\(NSRegularExpression.escapedPattern(for: word))\\b"
82
- highlightPattern(attributed, pattern: pattern, color: color)
83
- }
84
- }
85
-
86
- // MARK: - 数据结构
87
-
88
- struct SearchItem {
89
- let title: String
90
- let code: String
91
- let explanation: String
92
- let groupSize: Int
93
- }
94
-
95
- // MARK: - Combined Window Controller
96
-
97
- class CombinedSearchWindowController: NSObject, NSTableViewDataSource, NSTableViewDelegate, NSSearchFieldDelegate {
98
- private var panel: NSPanel!
99
- private var tableView: NSTableView!
100
- private var searchField: NSSearchField!
101
- private var codeTextView: NSTextView!
102
- private var allItems: [SearchItem] = []
103
- private var filteredItems: [SearchItem] = []
104
- private var selectedIndex: Int = -1
105
- private var confirmed: Bool = false
106
-
107
- enum Result {
108
- case confirmed(Int) // 用户确认插入,返回选中的索引
109
- case cancelled // 用户取消
110
- }
111
-
112
- func show(items: [SearchItem], keyword: String) -> Result {
113
- self.allItems = items
114
- self.filteredItems = items
115
-
116
- let panelWidth: CGFloat = 1400
117
- let panelHeight: CGFloat = 680
118
-
119
- // 创建面板
120
- panel = NSPanel(
121
- contentRect: NSRect(x: 0, y: 0, width: panelWidth, height: panelHeight),
122
- styleMask: [.titled, .closable, .resizable, .fullSizeContentView],
123
- backing: .buffered,
124
- defer: false
125
- )
126
- panel.title = "AutoSnippet 搜索结果"
127
- panel.level = .floating
128
- panel.center()
129
- panel.minSize = NSSize(width: 1000, height: 500)
130
- panel.titlebarAppearsTransparent = true
131
- panel.isMovableByWindowBackground = true
132
-
133
- // 模糊背景
134
- let visualEffect = NSVisualEffectView(frame: NSRect(x: 0, y: 0, width: panelWidth, height: panelHeight))
135
- visualEffect.material = .hudWindow
136
- visualEffect.blendingMode = .behindWindow
137
- visualEffect.state = .active
138
- visualEffect.autoresizingMask = [.width, .height]
139
- panel.contentView = visualEffect
140
-
141
- let contentView = NSView(frame: NSRect(x: 0, y: 0, width: panelWidth, height: panelHeight))
142
- contentView.autoresizingMask = [.width, .height]
143
- visualEffect.addSubview(contentView)
144
-
145
- // 顶部标题和搜索框
146
- let titleLabel = NSTextField(labelWithString: "找到 \(items.count) 个匹配")
147
- titleLabel.font = NSFont.systemFont(ofSize: 13, weight: .medium)
148
- titleLabel.textColor = .secondaryLabelColor
149
- titleLabel.frame = NSRect(x: 20, y: panelHeight - 50, width: 200, height: 20)
150
- titleLabel.autoresizingMask = [.minYMargin]
151
- contentView.addSubview(titleLabel)
152
-
153
- // 搜索框
154
- searchField = NSSearchField(frame: NSRect(x: 20, y: panelHeight - 85, width: 350, height: 28))
155
- searchField.placeholderString = "过滤结果..."
156
- searchField.stringValue = keyword
157
- searchField.autoresizingMask = [.width, .minYMargin]
158
- searchField.target = self
159
- searchField.action = #selector(searchFieldDidChange)
160
- searchField.font = NSFont.systemFont(ofSize: 13)
161
- contentView.addSubview(searchField)
162
-
163
- // 分隔线
164
- let separator = NSBox(frame: NSRect(x: 0, y: panelHeight - 95, width: panelWidth, height: 1))
165
- separator.boxType = .separator
166
- separator.autoresizingMask = [.width, .minYMargin]
167
- contentView.addSubview(separator)
168
-
169
- // 左侧:列表区域
170
- let listWidth: CGFloat = 360
171
- let listScrollView = NSScrollView(frame: NSRect(x: 20, y: 70, width: listWidth, height: panelHeight - 175))
172
- listScrollView.hasVerticalScroller = true
173
- listScrollView.borderType = .noBorder
174
- listScrollView.autoresizingMask = [.height, .minYMargin]
175
- listScrollView.wantsLayer = true
176
- listScrollView.layer?.cornerRadius = 8
177
- listScrollView.layer?.masksToBounds = true
178
- listScrollView.backgroundColor = NSColor(calibratedRed: 0.12, green: 0.12, blue: 0.14, alpha: 1.0)
179
- // 使用现代 overlay 滚动条样式(悬浮自动隐藏)
180
- listScrollView.scrollerStyle = .overlay
181
- listScrollView.autohidesScrollers = true
182
- listScrollView.scrollerKnobStyle = .light
183
- // 强制刷新滚动条样式
184
- listScrollView.flashScrollers()
185
-
186
- tableView = NSTableView()
187
- tableView.headerView = nil
188
- tableView.rowHeight = 60
189
- tableView.intercellSpacing = NSSize(width: 0, height: 2)
190
- tableView.backgroundColor = NSColor(calibratedRed: 0.12, green: 0.12, blue: 0.14, alpha: 1.0)
191
- tableView.usesAlternatingRowBackgroundColors = false
192
- tableView.selectionHighlightStyle = .regular
193
- tableView.target = self
194
- tableView.action = #selector(tableViewClicked)
195
- tableView.doubleAction = #selector(tableViewDoubleClicked)
196
-
197
- let column = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("item"))
198
- column.width = listWidth - 25
199
- tableView.addTableColumn(column)
200
-
201
- tableView.dataSource = self
202
- tableView.delegate = self
203
-
204
- listScrollView.documentView = tableView
205
- contentView.addSubview(listScrollView)
206
-
207
- // 右侧:代码预览区域
208
- let codeX = 20 + listWidth + 15
209
- let codeWidth = panelWidth - codeX - 20
210
-
211
- let previewLabel = NSTextField(labelWithString: "代码预览")
212
- previewLabel.font = NSFont.systemFont(ofSize: 11, weight: .semibold)
213
- previewLabel.textColor = .secondaryLabelColor
214
- previewLabel.frame = NSRect(x: codeX, y: panelHeight - 115, width: codeWidth, height: 16)
215
- previewLabel.autoresizingMask = [.width, .minYMargin]
216
- contentView.addSubview(previewLabel)
217
-
218
- let codeScrollView = NSScrollView(frame: NSRect(x: codeX, y: 70, width: codeWidth, height: panelHeight - 195))
219
- codeScrollView.hasVerticalScroller = true
220
- codeScrollView.hasHorizontalScroller = true
221
- codeScrollView.borderType = .noBorder
222
- codeScrollView.autoresizingMask = [.width, .height]
223
- codeScrollView.wantsLayer = true
224
- codeScrollView.layer?.cornerRadius = 8
225
- codeScrollView.layer?.masksToBounds = true
226
- codeScrollView.backgroundColor = NSColor(calibratedRed: 0.12, green: 0.12, blue: 0.14, alpha: 1.0)
227
- // 使用现代 overlay 滚动条样式(悬浮自动隐藏)
228
- codeScrollView.scrollerStyle = .overlay
229
- codeScrollView.autohidesScrollers = true
230
- codeScrollView.scrollerKnobStyle = .light
231
- // 启用平滑滚动
232
- codeScrollView.usesPredominantAxisScrolling = false
233
- codeScrollView.horizontalScrollElasticity = .automatic
234
- codeScrollView.verticalScrollElasticity = .automatic
235
- // 强制刷新滚动条样式
236
- codeScrollView.flashScrollers()
237
-
238
- codeTextView = NSTextView(frame: codeScrollView.bounds)
239
- codeTextView.isEditable = false
240
- codeTextView.isSelectable = true
241
- codeTextView.backgroundColor = NSColor(calibratedRed: 0.12, green: 0.12, blue: 0.14, alpha: 1.0)
242
- codeTextView.textContainerInset = NSSize(width: 16, height: 16)
243
- codeTextView.isHorizontallyResizable = true
244
- codeTextView.isVerticallyResizable = true
245
- codeTextView.textContainer?.widthTracksTextView = false
246
- codeTextView.textContainer?.heightTracksTextView = false
247
- codeTextView.textContainer?.containerSize = NSSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
248
- codeTextView.autoresizingMask = []
249
- codeTextView.textContainer?.lineFragmentPadding = 10
250
- // 设置默认字体大小
251
- codeTextView.font = NSFont(name: "SFMono-Regular", size: 14) ?? NSFont.monospacedSystemFont(ofSize: 14, weight: .regular)
252
-
253
- if let textContainer = codeTextView.textContainer {
254
- textContainer.size = NSSize(width: codeScrollView.bounds.width - 40, height: CGFloat.greatestFiniteMagnitude)
255
- }
256
-
257
- codeScrollView.documentView = codeTextView
258
- contentView.addSubview(codeScrollView)
259
-
260
- // 底部按钮
261
- let buttonY: CGFloat = 20
262
-
263
- let cancelButton = NSButton(title: "取消", target: self, action: #selector(cancelClicked))
264
- cancelButton.bezelStyle = .rounded
265
- cancelButton.frame = NSRect(x: panelWidth - 310, y: buttonY, width: 90, height: 32)
266
- cancelButton.keyEquivalent = "\u{1b}"
267
- cancelButton.autoresizingMask = [.minXMargin, .maxYMargin]
268
- cancelButton.wantsLayer = true
269
- cancelButton.layer?.cornerRadius = 6
270
- contentView.addSubview(cancelButton)
271
-
272
- let copyButton = NSButton(title: "复制代码", target: self, action: #selector(copyClicked))
273
- copyButton.bezelStyle = .rounded
274
- copyButton.frame = NSRect(x: panelWidth - 210, y: buttonY, width: 90, height: 32)
275
- copyButton.autoresizingMask = [.minXMargin, .maxYMargin]
276
- copyButton.wantsLayer = true
277
- copyButton.layer?.cornerRadius = 6
278
- contentView.addSubview(copyButton)
279
-
280
- let okButton = NSButton(title: "立即插入", target: self, action: #selector(okClicked))
281
- okButton.bezelStyle = .rounded
282
- okButton.frame = NSRect(x: panelWidth - 110, y: buttonY, width: 90, height: 32)
283
- okButton.keyEquivalent = "\r"
284
- okButton.autoresizingMask = [.minXMargin, .maxYMargin]
285
- okButton.wantsLayer = true
286
- okButton.layer?.cornerRadius = 6
287
- contentView.addSubview(okButton)
288
-
289
- // 初始化显示
290
- tableView.reloadData()
291
- if !filteredItems.isEmpty {
292
- tableView.selectRowIndexes(IndexSet(integer: 0), byExtendingSelection: false)
293
- updatePreview(index: 0)
294
- } else {
295
- showEmptyPreview()
296
- }
297
-
298
- // 显示窗口
299
- panel.alphaValue = 0
300
- NSApp.activate(ignoringOtherApps: true)
301
- panel.makeKeyAndOrderFront(nil)
302
- panel.makeFirstResponder(searchField)
303
-
304
- NSAnimationContext.runAnimationGroup({ context in
305
- context.duration = 0.2
306
- panel.animator().alphaValue = 1.0
307
- })
308
-
309
- NSApp.runModal(for: panel)
310
-
311
- // 返回结果
312
- if confirmed && selectedIndex >= 0 {
313
- // 找到在原始数组中的索引
314
- if let selected = filteredItems[safe: selectedIndex] {
315
- if let originalIndex = allItems.firstIndex(where: { $0.title == selected.title }) {
316
- return .confirmed(originalIndex)
317
- }
318
- }
319
- return .confirmed(selectedIndex)
320
- }
321
- return .cancelled
322
- }
323
-
324
- // MARK: - Table View Data Source
325
-
326
- func numberOfRows(in tableView: NSTableView) -> Int {
327
- return filteredItems.count
328
- }
329
-
330
- func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
331
- let item = filteredItems[row]
332
-
333
- let cellView = NSTableCellView()
334
- cellView.wantsLayer = true
335
-
336
- // 标题 - 自动折行,最多两行,右侧留出足够边距
337
- let textField = NSTextField(labelWithString: item.title)
338
- textField.font = NSFont.systemFont(ofSize: 14, weight: .medium)
339
- textField.textColor = .labelColor
340
- textField.lineBreakMode = .byWordWrapping
341
- textField.maximumNumberOfLines = 2 // 必须两行保证显示全
342
- textField.frame = NSRect(x: 12, y: 24, width: tableView.bounds.width - 56, height: 36)
343
- cellView.addSubview(textField)
344
-
345
- // 说明
346
- var subtitleParts: [String] = []
347
- if item.groupSize > 1 {
348
- subtitleParts.append("同类\(item.groupSize)")
349
- }
350
- if !item.explanation.isEmpty {
351
- subtitleParts.append(item.explanation)
352
- }
353
- let subtitle = subtitleParts.joined(separator: " · ")
354
- if !subtitle.isEmpty {
355
- let subtitleField = NSTextField(labelWithString: subtitle)
356
- subtitleField.font = NSFont.systemFont(ofSize: 11, weight: .regular)
357
- subtitleField.textColor = .secondaryLabelColor
358
- subtitleField.lineBreakMode = .byTruncatingTail
359
- subtitleField.frame = NSRect(x: 12, y: 8, width: tableView.bounds.width - 56, height: 16)
360
- cellView.addSubview(subtitleField)
361
- }
362
-
363
- return cellView
364
- }
365
-
366
- func tableViewSelectionDidChange(_ notification: Notification) {
367
- let row = tableView.selectedRow
368
- if row >= 0 {
369
- selectedIndex = row
370
- updatePreview(index: row)
371
- }
372
- }
373
-
374
- // MARK: - Actions
375
-
376
- @objc func tableViewClicked() {
377
- let row = tableView.clickedRow
378
- if row >= 0 {
379
- selectedIndex = row
380
- updatePreview(index: row)
381
- }
382
- }
383
-
384
- @objc func tableViewDoubleClicked() {
385
- let row = tableView.clickedRow
386
- if row >= 0 {
387
- selectedIndex = row
388
- confirmed = true
389
- panel.close()
390
- NSApp.stopModal()
391
- }
392
- }
393
-
394
- @objc func searchFieldDidChange() {
395
- let query = searchField.stringValue.lowercased().trimmingCharacters(in: .whitespaces)
396
-
397
- if query.isEmpty {
398
- filteredItems = allItems
399
- } else {
400
- filteredItems = allItems.filter { $0.title.lowercased().contains(query) }
401
- }
402
-
403
- tableView.reloadData()
404
-
405
- if !filteredItems.isEmpty {
406
- tableView.selectRowIndexes(IndexSet(integer: 0), byExtendingSelection: false)
407
- selectedIndex = 0
408
- updatePreview(index: 0)
409
- } else {
410
- showEmptyPreview()
411
- }
412
- }
413
-
414
- @objc func cancelClicked() {
415
- confirmed = false
416
- panel.close()
417
- NSApp.stopModal()
418
- }
419
-
420
- @objc func copyClicked() {
421
- if selectedIndex >= 0, let item = filteredItems[safe: selectedIndex] {
422
- let pureCode = extractPureCode(item.code)
423
- let pasteboard = NSPasteboard.general
424
- pasteboard.clearContents()
425
- pasteboard.setString(pureCode, forType: .string)
426
- print("✅ 代码已复制到剪贴板")
427
- // 复制后也关闭窗口
428
- confirmed = true
429
- panel.close()
430
- NSApp.stopModal()
431
- }
432
- }
433
-
434
- @objc func okClicked() {
435
- if selectedIndex >= 0 {
436
- confirmed = true
437
- // 复制代码到剪贴板
438
- if let item = filteredItems[safe: selectedIndex] {
439
- let pureCode = extractPureCode(item.code)
440
- let pasteboard = NSPasteboard.general
441
- pasteboard.clearContents()
442
- pasteboard.setString(pureCode, forType: .string)
443
- }
444
- panel.close()
445
- NSApp.stopModal()
446
- }
447
- }
448
-
449
- // MARK: - Helper Methods
450
-
451
- private func updatePreview(index: Int) {
452
- guard let item = filteredItems[safe: index] else {
453
- showEmptyPreview()
454
- return
455
- }
456
-
457
- let processedCode = item.code.replacingOccurrences(of: "\\n", with: "\n")
458
- let highlightedCode = SyntaxHighlighter.highlight(processedCode)
459
- codeTextView.textStorage?.setAttributedString(highlightedCode)
460
- }
461
-
462
- private func showEmptyPreview() {
463
- let emptyText = NSAttributedString(
464
- string: "无内容",
465
- attributes: [
466
- .font: NSFont.systemFont(ofSize: 14),
467
- .foregroundColor: NSColor.tertiaryLabelColor
468
- ]
469
- )
470
- codeTextView.textStorage?.setAttributedString(emptyText)
471
- }
472
-
473
- private func extractPureCode(_ code: String) -> String {
474
- var lines = code.split(separator: "\n", omittingEmptySubsequences: false).map(String.init)
475
-
476
- // 移除首尾的代码块标记(```swift、```等)
477
- while !lines.isEmpty && (lines.first?.trimmingCharacters(in: .whitespaces).starts(with: "```") ?? false) {
478
- lines.removeFirst()
479
- }
480
- while !lines.isEmpty && (lines.last?.trimmingCharacters(in: .whitespaces).starts(with: "```") ?? false) {
481
- lines.removeLast()
482
- }
483
-
484
- return lines.joined(separator: "\n")
485
- }
486
- }
487
-
488
- // MARK: - Array Safe Subscript
489
-
490
- extension Array {
491
- subscript(safe index: Int) -> Element? {
492
- return indices.contains(index) ? self[index] : nil
493
- }
494
- }