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
9 changes: 8 additions & 1 deletion Sources/CodecCore/Preferences.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ public final class Preferences: ObservableObject {
@Published public var shellPath: String { didSet { store(shellPath, .shellPath) } }
@Published public var showSystemStats: Bool { didSet { store(showSystemStats, .showSystemStats) } }
@Published public var prompt: PromptConfiguration { didSet { storePrompt() } }
/// How far the trackpad and the mouse wheel scroll, as a multiple of the system's own rate. At 1×,
/// SwiftTerm's, the scrollback follows the fingers and a wheel notch moves one line: many swipes to
/// get through a long output.
@Published public var scrollSpeed: Double { didSet { store(scrollSpeed, .scrollSpeed) } }
public static let scrollSpeeds: ClosedRange<Double> = 1...5
/// Copies text to the clipboard as soon as it's selected.
@Published public var copyOnSelect: Bool { didSet { store(copyOnSelect, .copyOnSelect) } }
/// Shows the rest of a matching earlier command, dimmed, as you type (zsh).
Expand Down Expand Up @@ -74,7 +79,7 @@ public final class Preferences: ObservableObject {
codecSounds, paneSounds, soundVolume, optionAsMeta, exitBehavior, shellIntegration, shellPath,
showSystemStats, prompt, copyOnSelect, suggestCommands, historyKeys, friendlyCompletion, colorFileListings,
ezaForLs, recordHistory, restoreWindows, restorePaneContents, showUsualDuration, activateVenvs, explainFailures,
checkForUpdates, commandSeparators, shadeAlternateCommands
checkForUpdates, commandSeparators, shadeAlternateCommands, scrollSpeed
}

private static let factoryDefaults: [Key: Any] = [
Expand All @@ -97,6 +102,7 @@ public final class Preferences: ObservableObject {
.shellIntegration: true,
.shellPath: "",
.showSystemStats: true,
.scrollSpeed: 2.0,
.copyOnSelect: false,
.suggestCommands: true,
.historyKeys: true,
Expand Down Expand Up @@ -134,6 +140,7 @@ public final class Preferences: ObservableObject {
shellIntegration = defaults.bool(forKey: Key.shellIntegration.rawValue)
shellPath = defaults.string(forKey: Key.shellPath.rawValue) ?? ""
showSystemStats = defaults.bool(forKey: Key.showSystemStats.rawValue)
scrollSpeed = Self.clamp(defaults.double(forKey: Key.scrollSpeed.rawValue), Self.scrollSpeeds, fallback: 2)
copyOnSelect = defaults.bool(forKey: Key.copyOnSelect.rawValue)
suggestCommands = defaults.bool(forKey: Key.suggestCommands.rawValue)
historyKeys = defaults.bool(forKey: Key.historyKeys.rawValue)
Expand Down
1 change: 1 addition & 0 deletions Sources/CodecUI/Pane.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1439,6 +1439,7 @@ final class Pane: LocalProcessTerminalViewDelegate {
terminal.selectedTextBackgroundColor = theme.selection
terminal.installColors(theme.terminalPalette)
terminal.optionAsMetaKey = preferences.optionAsMeta
terminal.scrollSensitivity = CGFloat(preferences.scrollSpeed)
view.apply(theme: theme, scanlines: preferences.scanlines)
refreshCommandSeparators()
}
Expand Down
15 changes: 15 additions & 0 deletions Sources/CodecUI/SettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,21 @@ private struct ShellSettings: View {
Text("Codec keeps where each command ran, how long it took and whether it failed, alongside your shell’s own history. Commands typed with a leading space aren’t kept. Search it with ⌘Y.")
.foregroundStyle(.secondary)
}
Section {
LabeledContent("Speed") {
HStack {
Slider(value: $prefs.scrollSpeed, in: Preferences.scrollSpeeds, step: 0.5)
Text(prefs.scrollSpeed.formatted(.number.precision(.fractionLength(0...1))) + "×")
.monospacedDigit()
.frame(width: 44, alignment: .trailing)
}
}
} header: {
Text("Scrolling")
} footer: {
Text("How far the trackpad and the mouse wheel scroll: at 1× the scrollback follows your fingers and moves a line for each notch of the wheel. Also applies in full-screen programs such as vim and less.")
.foregroundStyle(.secondary)
}
Section {
Toggle("Copy text as soon as it’s selected", isOn: $prefs.copyOnSelect)
} header: {
Expand Down
5 changes: 5 additions & 0 deletions Tests/CodecCoreTests/PoliciesTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ struct PreferencesTests {
#expect(!preferences.scanlines)
#expect(preferences.commandSeparators)
#expect(preferences.shadeAlternateCommands)
#expect(preferences.scrollSpeed == 2)
}

@Test func persistsChangesAcrossInstances() {
Expand All @@ -88,12 +89,14 @@ struct PreferencesTests {
first.bell = .codecLock
first.exitBehavior = .keepOpen
first.shellPath = "/bin/bash"
first.scrollSpeed = 3.5
let second = Preferences(defaults: defaults)
#expect(second.themeID == "amber")
#expect(second.fontSize == 15)
#expect(second.bell == .codecLock)
#expect(second.exitBehavior == .keepOpen)
#expect(second.shellPath == "/bin/bash")
#expect(second.scrollSpeed == 3.5)
}

@Test func repairsInvalidStoredValues() {
Expand All @@ -102,10 +105,12 @@ struct PreferencesTests {
defaults.set(500.0, forKey: "fontSize")
defaults.set("loud", forKey: "bell")
defaults.set(-2.0, forKey: "opacity")
defaults.set(0.0, forKey: "scrollSpeed")
let preferences = Preferences(defaults: defaults)
#expect(preferences.fontSize == 13)
#expect(preferences.bell == .visual)
#expect(preferences.opacity == 0.9)
#expect(preferences.scrollSpeed == 2)
}

@Test func postsAChangeNotification() {
Expand Down
29 changes: 29 additions & 0 deletions Tests/CodecUITests/PaneTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,35 @@ struct PaneTests {
pane.changeFontSize(by: -100)
#expect(pane.fontSize == 7)
}

// MARK: Scrolling

@Test func aWheelNotchScrollsAsFarAsTheSettingSays() async throws {
let env = try TestEnvironment()
defer { env.tearDown() }
let pane = env.makePane()
defer { pane.shutDown() }
let window = makeTestWindow()
defer { window.close() }
window.contentView = pane.view
#expect(await waitUntil { pane.terminal.process.running })
pane.terminal.feed(text: (1...200).map { "line \($0)\r\n" }.joined())
func top() -> Int? { pane.terminal.terminalContentSnapshot(region: .viewport)?.rows.first?.absoluteRow }
func notchUp() throws {
let wheel = try #require(CGEvent(scrollWheelEvent2Source: nil, units: .line, wheelCount: 1, wheel1: 1, wheel2: 0, wheel3: 0))
pane.terminal.scrollWheel(with: try #require(NSEvent(cgEvent: wheel)))
}
let bottom = try #require(top())
#expect(bottom > 10)

// Twice the system's rate unless set otherwise.
try notchUp()
#expect(top() == bottom - 2)
env.preferences.scrollSpeed = 4
pane.applyPreferences()
try notchUp()
#expect(top() == bottom - 6)
}
}

@Suite("Pane context menu", .serialized, .timeLimit(.minutes(1)))
Expand Down
2 changes: 1 addition & 1 deletion docs/command-line.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Hold ⌘ to see what opens. Every file and folder name on screen is washed in th

![⌘ held over the output of ls: every name marked, one tagged with the file that opens](images/open-file.png)

Copy on select is a setting (Settings › Shell › Selection). The scrollback keeps 10,000 lines; ⌘K clears it.
Copy on select is a setting (Settings › Shell › Selection). The scrollback keeps 10,000 lines; ⌘K clears it. The trackpad and the mouse wheel scroll it twice as far as elsewhere on the Mac; Settings › Shell › Scrolling sets how far.

⌘F searches the scrollback. Every match on screen is tinted and the current one is outlined; the bar counts them ("3 of 14"), and marks down the pane's right edge show where the rest are. Return or ⌘G goes to the next match, ⇧Return or ⇧⌘G to the previous one (↓ and ↑ do too), wrapping around; Esc closes the bar. **Aa** matches case, **ab** whole words, **.\*** a regular expression. With text selected, ⌘F (or ⌘E) searches for it. Matches keep up as the pane prints more.

Expand Down
1 change: 1 addition & 0 deletions docs/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Open Settings with ⌘,. Changes apply as you make them, unless a setting says i
- **Project environments**: whether to [activate a project's Python environment](prompt-and-status-bar.md#project-environments-zsh) (`.venv`) when you go into it, and your answer for each project: change it, forget it so Codec asks again, or remove the ones about projects that can't be found anymore, which are flagged.
- **Apple Intelligence**: whether failed commands offer to be [explained](commands-sidebar.md#why-did-it-fail) by Apple's on-device model.
- **History**: whether Codec records the commands you run, and a button to forget everything recorded so far. Forgetting also hides older commands from your shell's history file in Codec, without changing the file.
- **Scrolling**: how far the trackpad and the mouse wheel scroll, from 1× (the scrollback follows your fingers, a line per notch of the wheel) to 5×. 2× by default.
- **Selection**: copy text as soon as it's selected.

## Prompt
Expand Down
Loading