Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion Services/ClipboardStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,19 @@ class ClipboardStore: ObservableObject {

private func performAdd(_ item: ClipboardItem) {
print("[Buffer] Store: Adding item, current count: \(items.count)")


// Deduplication: if an identical inline text entry already exists, promote it
// to the top instead of adding a duplicate. File-backed large text is already
// deduplicated consecutively via hash in ClipboardWatcher.
if SettingsManager.shared.deduplicateHistory,
item.type == .text,
let newText = item.textContent,
!item.isFileBacked,
let existing = items.first(where: { $0.type == .text && !$0.isFileBacked && $0.textContent == newText }) {
moveToTop(existing)
return
}

// Insert at beginning (newest first)
items.insert(item, at: 0)

Expand Down
3 changes: 3 additions & 0 deletions Services/ClipboardWatcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ class ClipboardWatcher: ObservableObject {

// Try to capture text first
if let text = pasteboard.string(forType: .string), !text.isEmpty {
// Drop entries that are shorter than the configured minimum length
guard text.count >= SettingsManager.shared.minTextLength else { return }

let textSize = text.utf8.count

// Use prefix hash for large text to avoid expensive full-string hashing
Expand Down
22 changes: 16 additions & 6 deletions Services/SettingsManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,36 +11,46 @@ class SettingsManager: ObservableObject {
// Keys
private let hotkeyModifiersKey = "hotkeyModifiers"
private let hotkeyKeyCodeKey = "hotkeyKeyCode"

private let minTextLengthKey = "minTextLength"
private let deduplicateHistoryKey = "deduplicateHistory"

@Published var hotkeyModifiers: HotkeyModifiers
@Published var hotkeyKeyCode: UInt16
@Published var launchAtLogin: Bool = false

@Published var minTextLength: Int
@Published var deduplicateHistory: Bool

private init() {
// Initialize with defaults first, then load saved values
let defaultMods = HotkeyModifiers(shift: true, command: true, option: false, control: false)
let defaultKeyCode: UInt16 = 9 // V key

// Load saved modifiers or use default
if let savedMods = defaults.array(forKey: hotkeyModifiersKey) as? [String] {
self.hotkeyModifiers = HotkeyModifiers(from: savedMods)
} else {
self.hotkeyModifiers = defaultMods
}

// Load saved keycode or use default (V key)
let savedKeyCode = defaults.integer(forKey: hotkeyKeyCodeKey)
self.hotkeyKeyCode = savedKeyCode > 0 ? UInt16(savedKeyCode) : defaultKeyCode

// Load launch at login status
if #available(macOS 13.0, *) {
self.launchAtLogin = SMAppService.mainApp.status == .enabled
}

// Load history filter settings
self.minTextLength = defaults.object(forKey: minTextLengthKey) as? Int ?? 1
self.deduplicateHistory = defaults.bool(forKey: deduplicateHistoryKey)
}

func save() {
defaults.set(hotkeyModifiers.toArray(), forKey: hotkeyModifiersKey)
defaults.set(Int(hotkeyKeyCode), forKey: hotkeyKeyCodeKey)
defaults.set(minTextLength, forKey: minTextLengthKey)
defaults.set(deduplicateHistory, forKey: deduplicateHistoryKey)
}

func toggleLaunchAtLogin(_ enabled: Bool) {
Expand Down
63 changes: 50 additions & 13 deletions Views/SettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,34 +79,63 @@ struct SettingsView: View {
}

Divider()


// History section
VStack(alignment: .leading, spacing: 12) {
Text("History")
.font(.system(size: 13, weight: .medium))
.foregroundColor(.secondary)

HStack {
Text("Ignore entries shorter than")
.font(.system(size: 12))
Spacer()
Stepper(
settings.minTextLength == 1
? "1 character"
: "\(settings.minTextLength) characters",
value: $settings.minTextLength,
in: 1...20
)
.font(.system(size: 12))
.onChange(of: settings.minTextLength) { _ in settings.save() }
}

Toggle("Deduplicate history", isOn: $settings.deduplicateHistory)
.font(.system(size: 12))
.onChange(of: settings.deduplicateHistory) { _ in settings.save() }
.toggleStyle(.switch)
}

Divider()

// System section
VStack(alignment: .leading, spacing: 12) {
Text("System")
.font(.system(size: 13, weight: .medium))
.foregroundColor(.secondary)

Toggle("Launch at Login", isOn: $settings.launchAtLogin)
.font(.system(size: 12))
.onChange(of: settings.launchAtLogin) { newValue in
SettingsManager.shared.toggleLaunchAtLogin(newValue)
// Sync back state in case toggle fails
// Sync back state in case toggle fails
DispatchQueue.main.async {
settings.launchAtLogin = SettingsManager.shared.launchAtLogin
}
}
.toggleStyle(.switch)
}

Spacer()

// Footer
Text("Changes take effect after restarting Buffer")
.font(.system(size: 10))
.foregroundColor(.secondary)
}
.padding(20)
.frame(width: 320, height: 280)
.frame(width: 320, height: 400)
.background(KeyRecorder(isRecording: $isRecording) { keyCode, modifiers in
settings.hotkeyKeyCode = keyCode
settings.hotkeyModifiers = modifiers
Expand Down Expand Up @@ -187,35 +216,43 @@ class SettingsViewModel: ObservableObject {
@Published var hotkeyModifiers: HotkeyModifiers
@Published var hotkeyKeyCode: UInt16
@Published var launchAtLogin: Bool

@Published var minTextLength: Int
@Published var deduplicateHistory: Bool

private let defaults = UserDefaults.standard
private let hotkeyModifiersKey = "hotkeyModifiers"
private let hotkeyKeyCodeKey = "hotkeyKeyCode"

init() {
// Load modifiers
if let savedMods = defaults.array(forKey: hotkeyModifiersKey) as? [String] {
self.hotkeyModifiers = HotkeyModifiers(from: savedMods)
} else {
self.hotkeyModifiers = HotkeyModifiers(shift: true, command: true, option: false, control: false)
}

// Load keycode (default to V = 9)
let savedKeyCode = defaults.integer(forKey: hotkeyKeyCodeKey)
self.hotkeyKeyCode = savedKeyCode > 0 ? UInt16(savedKeyCode) : 9

// Load launch at login status from manager natively via SMAppService
self.launchAtLogin = SettingsManager.shared.launchAtLogin

// Load history filter settings
self.minTextLength = SettingsManager.shared.minTextLength
self.deduplicateHistory = SettingsManager.shared.deduplicateHistory
}

func save() {
defaults.set(hotkeyModifiers.toArray(), forKey: hotkeyModifiersKey)
defaults.set(Int(hotkeyKeyCode), forKey: hotkeyKeyCodeKey)

SettingsManager.shared.hotkeyModifiers = hotkeyModifiers
SettingsManager.shared.hotkeyKeyCode = hotkeyKeyCode
SettingsManager.shared.minTextLength = minTextLength
SettingsManager.shared.deduplicateHistory = deduplicateHistory
SettingsManager.shared.save()

NotificationCenter.default.post(name: .bufferHotkeyChanged, object: nil)
}
}