@arach/lattices 0.1.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.
- package/README.md +157 -0
- package/app/Lattices.app/Contents/Info.plist +24 -0
- package/app/Package.swift +13 -0
- package/app/Sources/App.swift +49 -0
- package/app/Sources/AppDelegate.swift +104 -0
- package/app/Sources/AppShellView.swift +62 -0
- package/app/Sources/AppTypeClassifier.swift +70 -0
- package/app/Sources/AppWindowShell.swift +63 -0
- package/app/Sources/CheatSheetHUD.swift +331 -0
- package/app/Sources/CommandModeState.swift +1341 -0
- package/app/Sources/CommandModeView.swift +1380 -0
- package/app/Sources/CommandModeWindow.swift +192 -0
- package/app/Sources/CommandPaletteView.swift +307 -0
- package/app/Sources/CommandPaletteWindow.swift +134 -0
- package/app/Sources/DaemonProtocol.swift +101 -0
- package/app/Sources/DaemonServer.swift +406 -0
- package/app/Sources/DesktopModel.swift +121 -0
- package/app/Sources/DesktopModelTypes.swift +71 -0
- package/app/Sources/DiagnosticLog.swift +253 -0
- package/app/Sources/EventBus.swift +29 -0
- package/app/Sources/HotkeyManager.swift +249 -0
- package/app/Sources/HotkeyStore.swift +330 -0
- package/app/Sources/InventoryManager.swift +35 -0
- package/app/Sources/InventoryPath.swift +43 -0
- package/app/Sources/KeyRecorderView.swift +210 -0
- package/app/Sources/LatticesApi.swift +915 -0
- package/app/Sources/MainView.swift +507 -0
- package/app/Sources/MainWindow.swift +70 -0
- package/app/Sources/OrphanRow.swift +129 -0
- package/app/Sources/PaletteCommand.swift +409 -0
- package/app/Sources/PermissionChecker.swift +115 -0
- package/app/Sources/Preferences.swift +48 -0
- package/app/Sources/ProcessModel.swift +199 -0
- package/app/Sources/ProcessQuery.swift +151 -0
- package/app/Sources/Project.swift +28 -0
- package/app/Sources/ProjectRow.swift +368 -0
- package/app/Sources/ProjectScanner.swift +121 -0
- package/app/Sources/ScreenMapState.swift +2397 -0
- package/app/Sources/ScreenMapView.swift +2817 -0
- package/app/Sources/ScreenMapWindowController.swift +89 -0
- package/app/Sources/SessionManager.swift +72 -0
- package/app/Sources/SettingsView.swift +641 -0
- package/app/Sources/SettingsWindow.swift +20 -0
- package/app/Sources/TabGroupRow.swift +178 -0
- package/app/Sources/Terminal.swift +259 -0
- package/app/Sources/TerminalQuery.swift +156 -0
- package/app/Sources/TerminalSynthesizer.swift +200 -0
- package/app/Sources/Theme.swift +124 -0
- package/app/Sources/TilePickerView.swift +209 -0
- package/app/Sources/TmuxModel.swift +53 -0
- package/app/Sources/TmuxQuery.swift +81 -0
- package/app/Sources/WindowTiler.swift +1752 -0
- package/app/Sources/WorkspaceManager.swift +434 -0
- package/bin/daemon-client.js +187 -0
- package/bin/lattices-app.js +205 -0
- package/bin/lattices.js +1295 -0
- package/docs/api.md +707 -0
- package/docs/app.md +250 -0
- package/docs/concepts.md +225 -0
- package/docs/config.md +234 -0
- package/docs/layers.md +317 -0
- package/docs/overview.md +74 -0
- package/docs/quickstart.md +82 -0
- package/package.json +38 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import AppKit
|
|
2
|
+
import SwiftUI
|
|
3
|
+
|
|
4
|
+
// MARK: - CheatSheetHUD (singleton window controller)
|
|
5
|
+
|
|
6
|
+
final class CheatSheetHUD {
|
|
7
|
+
static let shared = CheatSheetHUD()
|
|
8
|
+
|
|
9
|
+
private var panel: NSPanel?
|
|
10
|
+
private var localMonitor: Any?
|
|
11
|
+
private var globalMonitor: Any?
|
|
12
|
+
|
|
13
|
+
var isVisible: Bool { panel?.isVisible ?? false }
|
|
14
|
+
|
|
15
|
+
func toggle() {
|
|
16
|
+
if isVisible {
|
|
17
|
+
dismiss()
|
|
18
|
+
} else {
|
|
19
|
+
show()
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
func show() {
|
|
24
|
+
guard panel == nil else { return }
|
|
25
|
+
|
|
26
|
+
let view = CheatSheetView()
|
|
27
|
+
.preferredColorScheme(.dark)
|
|
28
|
+
|
|
29
|
+
let hosting = NSHostingView(rootView: view)
|
|
30
|
+
|
|
31
|
+
let p = NSPanel(
|
|
32
|
+
contentRect: NSRect(x: 0, y: 0, width: 520, height: 420),
|
|
33
|
+
styleMask: [.borderless, .nonactivatingPanel],
|
|
34
|
+
backing: .buffered,
|
|
35
|
+
defer: false
|
|
36
|
+
)
|
|
37
|
+
p.isOpaque = false
|
|
38
|
+
p.backgroundColor = .clear
|
|
39
|
+
p.level = .floating
|
|
40
|
+
p.hasShadow = true
|
|
41
|
+
p.hidesOnDeactivate = false
|
|
42
|
+
p.isReleasedWhenClosed = false
|
|
43
|
+
p.isMovableByWindowBackground = false
|
|
44
|
+
p.contentView = hosting
|
|
45
|
+
|
|
46
|
+
// Center on the screen containing the mouse cursor
|
|
47
|
+
let mouseLocation = NSEvent.mouseLocation
|
|
48
|
+
let screen = NSScreen.screens.first(where: { $0.frame.contains(mouseLocation) }) ?? NSScreen.main ?? NSScreen.screens.first!
|
|
49
|
+
let screenFrame = screen.visibleFrame
|
|
50
|
+
let x = screenFrame.midX - 260
|
|
51
|
+
let y = screenFrame.midY - 210
|
|
52
|
+
p.setFrameOrigin(NSPoint(x: x, y: y))
|
|
53
|
+
|
|
54
|
+
p.alphaValue = 0
|
|
55
|
+
p.orderFrontRegardless()
|
|
56
|
+
|
|
57
|
+
NSAnimationContext.runAnimationGroup { ctx in
|
|
58
|
+
ctx.duration = 0.15
|
|
59
|
+
p.animator().alphaValue = 1.0
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
self.panel = p
|
|
63
|
+
installMonitors()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
func dismiss() {
|
|
67
|
+
guard let p = panel else { return }
|
|
68
|
+
removeMonitors()
|
|
69
|
+
|
|
70
|
+
NSAnimationContext.runAnimationGroup({ ctx in
|
|
71
|
+
ctx.duration = 0.2
|
|
72
|
+
p.animator().alphaValue = 0
|
|
73
|
+
}) { [weak self] in
|
|
74
|
+
p.orderOut(nil)
|
|
75
|
+
self?.panel = nil
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// MARK: - Event monitors
|
|
80
|
+
|
|
81
|
+
private func installMonitors() {
|
|
82
|
+
// Escape key dismisses (global — panel is non-activating so keys go to frontmost app)
|
|
83
|
+
localMonitor = NSEvent.addGlobalMonitorForEvents(matching: .keyDown) { [weak self] event in
|
|
84
|
+
if event.keyCode == 53 { // Escape
|
|
85
|
+
self?.dismiss()
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Click outside dismisses
|
|
90
|
+
globalMonitor = NSEvent.addGlobalMonitorForEvents(matching: [.leftMouseDown, .rightMouseDown]) { [weak self] _ in
|
|
91
|
+
self?.dismiss()
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private func removeMonitors() {
|
|
96
|
+
if let m = localMonitor { NSEvent.removeMonitor(m); localMonitor = nil }
|
|
97
|
+
if let m = globalMonitor { NSEvent.removeMonitor(m); globalMonitor = nil }
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// MARK: - CheatSheetView
|
|
103
|
+
|
|
104
|
+
struct CheatSheetView: View {
|
|
105
|
+
@ObservedObject private var hotkeyStore = HotkeyStore.shared
|
|
106
|
+
|
|
107
|
+
var body: some View {
|
|
108
|
+
VStack(spacing: 0) {
|
|
109
|
+
// Title
|
|
110
|
+
HStack {
|
|
111
|
+
Text("KEYBOARD SHORTCUTS")
|
|
112
|
+
.font(Typo.pixel(14))
|
|
113
|
+
.foregroundColor(Palette.textDim)
|
|
114
|
+
.tracking(1)
|
|
115
|
+
Spacer()
|
|
116
|
+
}
|
|
117
|
+
.padding(.horizontal, 20)
|
|
118
|
+
.padding(.top, 16)
|
|
119
|
+
.padding(.bottom, 10)
|
|
120
|
+
|
|
121
|
+
Rectangle().fill(Palette.border).frame(height: 0.5)
|
|
122
|
+
|
|
123
|
+
// Two-column body
|
|
124
|
+
HStack(alignment: .top, spacing: 20) {
|
|
125
|
+
// Left column: Tiling
|
|
126
|
+
tilingColumn
|
|
127
|
+
|
|
128
|
+
Rectangle().fill(Palette.border).frame(width: 0.5)
|
|
129
|
+
|
|
130
|
+
// Right column: App + tmux
|
|
131
|
+
VStack(alignment: .leading, spacing: 16) {
|
|
132
|
+
appColumn
|
|
133
|
+
Rectangle().fill(Palette.border).frame(height: 0.5)
|
|
134
|
+
tmuxColumn
|
|
135
|
+
}
|
|
136
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
137
|
+
}
|
|
138
|
+
.padding(.horizontal, 20)
|
|
139
|
+
.padding(.vertical, 14)
|
|
140
|
+
|
|
141
|
+
Spacer(minLength: 0)
|
|
142
|
+
|
|
143
|
+
Rectangle().fill(Palette.border).frame(height: 0.5)
|
|
144
|
+
|
|
145
|
+
// Footer
|
|
146
|
+
HStack {
|
|
147
|
+
Spacer()
|
|
148
|
+
Text("Press ESC to dismiss")
|
|
149
|
+
.font(Typo.caption(10))
|
|
150
|
+
.foregroundColor(Palette.textMuted)
|
|
151
|
+
Spacer()
|
|
152
|
+
}
|
|
153
|
+
.padding(.vertical, 8)
|
|
154
|
+
}
|
|
155
|
+
.frame(width: 520, height: 420)
|
|
156
|
+
.background(
|
|
157
|
+
RoundedRectangle(cornerRadius: 12)
|
|
158
|
+
.fill(Palette.bg)
|
|
159
|
+
.overlay(
|
|
160
|
+
RoundedRectangle(cornerRadius: 12)
|
|
161
|
+
.strokeBorder(Palette.borderLit, lineWidth: 0.5)
|
|
162
|
+
)
|
|
163
|
+
)
|
|
164
|
+
.clipShape(RoundedRectangle(cornerRadius: 12))
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// MARK: - Tiling Column
|
|
168
|
+
|
|
169
|
+
private var tilingColumn: some View {
|
|
170
|
+
VStack(alignment: .leading, spacing: 10) {
|
|
171
|
+
columnHeader("Tiling")
|
|
172
|
+
|
|
173
|
+
// 3x3 grid
|
|
174
|
+
VStack(spacing: 2) {
|
|
175
|
+
HStack(spacing: 2) {
|
|
176
|
+
tileCell(action: .tileTopLeft, label: "TL")
|
|
177
|
+
tileCell(action: .tileTop, label: "Top")
|
|
178
|
+
tileCell(action: .tileTopRight, label: "TR")
|
|
179
|
+
}
|
|
180
|
+
HStack(spacing: 2) {
|
|
181
|
+
tileCell(action: .tileLeft, label: "Left")
|
|
182
|
+
tileCell(action: .tileMaximize, label: "Max")
|
|
183
|
+
tileCell(action: .tileRight, label: "Right")
|
|
184
|
+
}
|
|
185
|
+
HStack(spacing: 2) {
|
|
186
|
+
tileCell(action: .tileBottomLeft, label: "BL")
|
|
187
|
+
tileCell(action: .tileBottom, label: "Bot")
|
|
188
|
+
tileCell(action: .tileBottomRight, label: "BR")
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
.padding(6)
|
|
192
|
+
.background(
|
|
193
|
+
RoundedRectangle(cornerRadius: 6)
|
|
194
|
+
.fill(Color.black.opacity(0.25))
|
|
195
|
+
.overlay(
|
|
196
|
+
RoundedRectangle(cornerRadius: 6)
|
|
197
|
+
.strokeBorder(Palette.border, lineWidth: 0.5)
|
|
198
|
+
)
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
// Thirds row
|
|
202
|
+
HStack(spacing: 2) {
|
|
203
|
+
tileCell(action: .tileLeftThird, label: "\u{2153}L")
|
|
204
|
+
tileCell(action: .tileCenterThird, label: "\u{2153}C")
|
|
205
|
+
tileCell(action: .tileRightThird, label: "\u{2153}R")
|
|
206
|
+
}
|
|
207
|
+
.padding(6)
|
|
208
|
+
.background(
|
|
209
|
+
RoundedRectangle(cornerRadius: 6)
|
|
210
|
+
.fill(Color.black.opacity(0.25))
|
|
211
|
+
.overlay(
|
|
212
|
+
RoundedRectangle(cornerRadius: 6)
|
|
213
|
+
.strokeBorder(Palette.border, lineWidth: 0.5)
|
|
214
|
+
)
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
// Center + Distribute
|
|
218
|
+
shortcutRow(action: .tileCenter)
|
|
219
|
+
shortcutRow(action: .tileDistribute)
|
|
220
|
+
}
|
|
221
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// MARK: - App Column
|
|
225
|
+
|
|
226
|
+
private var appColumn: some View {
|
|
227
|
+
VStack(alignment: .leading, spacing: 8) {
|
|
228
|
+
columnHeader("App")
|
|
229
|
+
|
|
230
|
+
shortcutRow(action: .palette)
|
|
231
|
+
shortcutRow(action: .screenMap)
|
|
232
|
+
shortcutRow(action: .bezel)
|
|
233
|
+
shortcutRow(action: .cheatSheet)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// MARK: - tmux Column
|
|
238
|
+
|
|
239
|
+
private var tmuxColumn: some View {
|
|
240
|
+
VStack(alignment: .leading, spacing: 6) {
|
|
241
|
+
columnHeader("Inside tmux")
|
|
242
|
+
|
|
243
|
+
tmuxRow("Detach", keys: ["Ctrl+B", "D"])
|
|
244
|
+
tmuxRow("Kill pane", keys: ["Ctrl+B", "X"])
|
|
245
|
+
tmuxRow("Pane left", keys: ["Ctrl+B", "\u{2190}"])
|
|
246
|
+
tmuxRow("Pane right", keys: ["Ctrl+B", "\u{2192}"])
|
|
247
|
+
tmuxRow("Zoom toggle", keys: ["Ctrl+B", "Z"])
|
|
248
|
+
tmuxRow("Scroll mode", keys: ["Ctrl+B", "["])
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// MARK: - Shared components
|
|
253
|
+
|
|
254
|
+
private func columnHeader(_ title: String) -> some View {
|
|
255
|
+
Text(title.uppercased())
|
|
256
|
+
.font(Typo.pixel(12))
|
|
257
|
+
.foregroundColor(Palette.textDim)
|
|
258
|
+
.tracking(1)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
private func tileCell(action: HotkeyAction, label: String) -> some View {
|
|
262
|
+
let binding = hotkeyStore.bindings[action]
|
|
263
|
+
let badgeText = binding?.displayParts.last ?? ""
|
|
264
|
+
|
|
265
|
+
return VStack(spacing: 3) {
|
|
266
|
+
Text(label)
|
|
267
|
+
.font(Typo.caption(9))
|
|
268
|
+
.foregroundColor(Palette.textDim)
|
|
269
|
+
Text(badgeText)
|
|
270
|
+
.font(Typo.geistMonoBold(9))
|
|
271
|
+
.foregroundColor(Palette.text)
|
|
272
|
+
}
|
|
273
|
+
.frame(maxWidth: .infinity)
|
|
274
|
+
.frame(height: 38)
|
|
275
|
+
.background(
|
|
276
|
+
RoundedRectangle(cornerRadius: 4)
|
|
277
|
+
.fill(Palette.surface)
|
|
278
|
+
.overlay(
|
|
279
|
+
RoundedRectangle(cornerRadius: 4)
|
|
280
|
+
.strokeBorder(Palette.border, lineWidth: 0.5)
|
|
281
|
+
)
|
|
282
|
+
)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
private func shortcutRow(action: HotkeyAction) -> some View {
|
|
286
|
+
let binding = hotkeyStore.bindings[action]
|
|
287
|
+
return HStack(spacing: 8) {
|
|
288
|
+
if let parts = binding?.displayParts {
|
|
289
|
+
HStack(spacing: 3) {
|
|
290
|
+
ForEach(parts, id: \.self) { part in
|
|
291
|
+
keyBadge(part)
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
Text(action.label)
|
|
296
|
+
.font(Typo.caption(11))
|
|
297
|
+
.foregroundColor(Palette.textDim)
|
|
298
|
+
Spacer()
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
private func tmuxRow(_ label: String, keys: [String]) -> some View {
|
|
303
|
+
HStack(spacing: 8) {
|
|
304
|
+
HStack(spacing: 3) {
|
|
305
|
+
ForEach(keys, id: \.self) { key in
|
|
306
|
+
keyBadge(key)
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
Text(label)
|
|
310
|
+
.font(Typo.caption(11))
|
|
311
|
+
.foregroundColor(Palette.textDim)
|
|
312
|
+
Spacer()
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
private func keyBadge(_ key: String) -> some View {
|
|
317
|
+
Text(key)
|
|
318
|
+
.font(Typo.geistMonoBold(10))
|
|
319
|
+
.foregroundColor(Palette.text)
|
|
320
|
+
.padding(.horizontal, 6)
|
|
321
|
+
.padding(.vertical, 3)
|
|
322
|
+
.background(
|
|
323
|
+
RoundedRectangle(cornerRadius: 3)
|
|
324
|
+
.fill(Palette.surface)
|
|
325
|
+
.overlay(
|
|
326
|
+
RoundedRectangle(cornerRadius: 3)
|
|
327
|
+
.strokeBorder(Palette.border, lineWidth: 0.5)
|
|
328
|
+
)
|
|
329
|
+
)
|
|
330
|
+
}
|
|
331
|
+
}
|