Skip to content

Find: Replace and Match Case Toggle #301

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,22 @@
"version" : "0.1.20"
}
},
{
"identity" : "codeeditsymbols",
"kind" : "remoteSourceControl",
"location" : "https://github.com/CodeEditApp/CodeEditSymbols.git",
"state" : {
"revision" : "ae69712b08571c4469c2ed5cd38ad9f19439793e",
"version" : "0.2.3"
}
},
{
"identity" : "codeedittextview",
"kind" : "remoteSourceControl",
"location" : "https://github.com/CodeEditApp/CodeEditTextView.git",
"state" : {
"revision" : "02202a8d925dc902f18626e953b3447e320253d1",
"version" : "0.8.1"
"revision" : "47faec9fb571c9c695897e69f0a4f08512ae682e",
"version" : "0.8.2"
}
},
{
Expand Down
8 changes: 7 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ let package = Package(
url: "https://github.com/CodeEditApp/CodeEditLanguages.git",
exact: "0.1.20"
),
// CodeEditSymbols
.package(
url: "https://github.com/CodeEditApp/CodeEditSymbols.git",
exact: "0.2.3"
),
// SwiftLint
.package(
url: "https://github.com/lukepistrol/SwiftLintPlugin",
Expand All @@ -42,7 +47,8 @@ let package = Package(
dependencies: [
"CodeEditTextView",
"CodeEditLanguages",
"TextFormation"
"TextFormation",
"CodeEditSymbols"
],
plugins: [
.plugin(name: "SwiftLint", package: "SwiftLintPlugin")
Expand Down
17 changes: 13 additions & 4 deletions Sources/CodeEditSourceEditor/CodeEditUI/PanelTextField.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,24 @@ struct PanelTextField<LeadingAccessories: View, TrailingAccessories: View>: View
Color(.textBackgroundColor)
} else {
if colorScheme == .light {
Color.black.opacity(0.06)
// TODO: if over sidebar 0.06 else 0.085
// Color.black.opacity(0.06)
Color.black.opacity(0.085)
} else {
Color.white.opacity(0.24)
// TODO: if over sidebar 0.24 else 0.06
// Color.white.opacity(0.24)
Color.white.opacity(0.06)
}
}
} else {
if colorScheme == .light {
Color.clear
// TODO: if over sidebar 0.0 else 0.06
// Color.clear
Color.black.opacity(0.06)
} else {
Color.white.opacity(0.14)
// TODO: if over sidebar 0.14 else 0.045
// Color.white.opacity(0.14)
Color.white.opacity(0.045)
}
}
}
Expand All @@ -98,6 +106,7 @@ struct PanelTextField<LeadingAccessories: View, TrailingAccessories: View>: View
Text(helperText)
.font(.caption)
.foregroundStyle(.secondary)
.lineLimit(1)
}
}
if clearable == true {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ extension TextViewController: FindPanelTarget {
gutterView.frame.origin.y = -scrollView.contentInsets.top
}

func findPanelModeDidChange(to mode: FindPanelMode, panelHeight: CGFloat) {
scrollView.contentInsets.top += mode == .replace ? panelHeight/2 : -panelHeight
gutterView.frame.origin.y = -scrollView.contentInsets.top
}

var emphasisManager: EmphasisManager? {
textView?.emphasisManager
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ extension TextViewController {
scrollView.contentInsets.top += additionalTextInsets?.top ?? 0
scrollView.contentInsets.bottom += additionalTextInsets?.bottom ?? 0

scrollView.contentInsets.top += (findViewController?.isShowingFindPanel ?? false) ? FindPanel.height : 0
scrollView.contentInsets.top += (findViewController?.isShowingFindPanel ?? false)
? findViewController?.panelHeight ?? 0
: 0
}
}
6 changes: 6 additions & 0 deletions Sources/CodeEditSourceEditor/Find/FindPanelDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,14 @@ protocol FindPanelDelegate: AnyObject {
func findPanelOnSubmit()
func findPanelOnDismiss()
func findPanelDidUpdate(_ searchText: String)
func findPanelDidUpdateMode(_ mode: FindPanelMode)
func findPanelDidUpdateWrapAround(_ wrapAround: Bool)
func findPanelDidUpdateMatchCase(_ matchCase: Bool)
func findPanelDidUpdateReplaceText(_ text: String)
func findPanelPrevButtonClicked()
func findPanelNextButtonClicked()
func findPanelReplaceButtonClicked()
func findPanelReplaceAllButtonClicked()
func findPanelUpdateMatchCount(_ count: Int)
func findPanelClearEmphasis()
}
1 change: 1 addition & 0 deletions Sources/CodeEditSourceEditor/Find/FindPanelTarget.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ protocol FindPanelTarget: AnyObject {

func findPanelWillShow(panelHeight: CGFloat)
func findPanelWillHide(panelHeight: CGFloat)
func findPanelModeDidChange(to mode: FindPanelMode, panelHeight: CGFloat)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import AppKit
import CodeEditTextView

extension FindViewController: FindPanelDelegate {
var findPanelMode: FindPanelMode { mode }
var findPanelWrapAround: Bool { wrapAround }
var findPanelMatchCase: Bool { matchCase }

func findPanelOnSubmit() {
findPanelNextButtonClicked()
}
Expand Down Expand Up @@ -61,54 +65,92 @@ extension FindViewController: FindPanelDelegate {
find(text: text)
}

func findPanelDidUpdateMode(_ mode: FindPanelMode) {
self.mode = mode
if isShowingFindPanel {
target?.findPanelModeDidChange(to: mode, panelHeight: panelHeight)
}
}

func findPanelDidUpdateWrapAround(_ wrapAround: Bool) {
self.wrapAround = wrapAround
}

func findPanelDidUpdateMatchCase(_ matchCase: Bool) {
self.matchCase = matchCase
if !findText.isEmpty {
performFind()
addEmphases()
}
}

func findPanelDidUpdateReplaceText(_ text: String) {
self.replaceText = text
}

private func flashCurrentMatch(emphasisManager: EmphasisManager, textViewController: TextViewController) {
let newActiveRange = findMatches[currentFindMatchIndex]
emphasisManager.removeEmphases(for: EmphasisGroup.find)
emphasisManager.addEmphasis(
Emphasis(
range: newActiveRange,
style: .standard,
flash: true,
inactive: false,
selectInDocument: true
),
for: EmphasisGroup.find
)
}

func findPanelPrevButtonClicked() {
guard let textViewController = target as? TextViewController,
let emphasisManager = target?.emphasisManager else { return }

// Check if there are any matches
if findMatches.isEmpty {
NSSound.beep()
BezelNotification.show(
symbolName: "arrow.up.to.line",
over: textViewController.textView
)
return
}

// Update to previous match
let oldIndex = currentFindMatchIndex
currentFindMatchIndex = (currentFindMatchIndex - 1 + findMatches.count) % findMatches.count

// Show bezel notification if we cycled from first to last match
if oldIndex == 0 && currentFindMatchIndex == findMatches.count - 1 {
// Check if we're at the first match and wrapAround is false
if !wrapAround && currentFindMatchIndex == 0 {
NSSound.beep()
BezelNotification.show(
symbolName: "arrow.trianglehead.bottomleft.capsulepath.clockwise",
symbolName: "arrow.up.to.line",
over: textViewController.textView
)
if textViewController.textView.window?.firstResponder === textViewController.textView {
flashCurrentMatch(emphasisManager: emphasisManager, textViewController: textViewController)
return
}
updateEmphasesForCurrentMatch(emphasisManager: emphasisManager)
return
}

// Update to previous match
currentFindMatchIndex = (currentFindMatchIndex - 1 + findMatches.count) % findMatches.count

// If the text view has focus, show a flash animation for the current match
if textViewController.textView.window?.firstResponder === textViewController.textView {
let newActiveRange = findMatches[currentFindMatchIndex]

// Clear existing emphases before adding the flash
emphasisManager.removeEmphases(for: EmphasisGroup.find)

emphasisManager.addEmphasis(
Emphasis(
range: newActiveRange,
style: .standard,
flash: true,
inactive: false,
selectInDocument: true
),
for: EmphasisGroup.find
)

flashCurrentMatch(emphasisManager: emphasisManager, textViewController: textViewController)
return
}

// Create updated emphases with new active state
updateEmphasesForCurrentMatch(emphasisManager: emphasisManager)
}

private func updateEmphasesForCurrentMatch(emphasisManager: EmphasisManager, flash: Bool = false) {
// Create updated emphases with current match emphasized
let updatedEmphases = findMatches.enumerated().map { index, range in
Emphasis(
range: range,
style: .standard,
flash: false,
flash: flash,
inactive: index != currentFindMatchIndex,
selectInDocument: index == currentFindMatchIndex
)
Expand All @@ -124,7 +166,6 @@ extension FindViewController: FindPanelDelegate {

// Check if there are any matches
if findMatches.isEmpty {
// Show "no matches" bezel notification and play beep
NSSound.beep()
BezelNotification.show(
symbolName: "arrow.down.to.line",
Expand All @@ -133,52 +174,41 @@ extension FindViewController: FindPanelDelegate {
return
}

// Update to next match
let oldIndex = currentFindMatchIndex
currentFindMatchIndex = (currentFindMatchIndex + 1) % findMatches.count

// Show bezel notification if we cycled from last to first match
if oldIndex == findMatches.count - 1 && currentFindMatchIndex == 0 {
// Check if we're at the last match and wrapAround is false
if !wrapAround && currentFindMatchIndex == findMatches.count - 1 {
NSSound.beep()
BezelNotification.show(
symbolName: "arrow.triangle.capsulepath",
symbolName: "arrow.down.to.line",
over: textViewController.textView
)
if textViewController.textView.window?.firstResponder === textViewController.textView {
flashCurrentMatch(emphasisManager: emphasisManager, textViewController: textViewController)
return
}
updateEmphasesForCurrentMatch(emphasisManager: emphasisManager)
return
}

// Update to next match
currentFindMatchIndex = (currentFindMatchIndex + 1) % findMatches.count

// If the text view has focus, show a flash animation for the current match
if textViewController.textView.window?.firstResponder === textViewController.textView {
let newActiveRange = findMatches[currentFindMatchIndex]

// Clear existing emphases before adding the flash
emphasisManager.removeEmphases(for: EmphasisGroup.find)

emphasisManager.addEmphasis(
Emphasis(
range: newActiveRange,
style: .standard,
flash: true,
inactive: false,
selectInDocument: true
),
for: EmphasisGroup.find
)

flashCurrentMatch(emphasisManager: emphasisManager, textViewController: textViewController)
return
}

// Create updated emphases with new active state
let updatedEmphases = findMatches.enumerated().map { index, range in
Emphasis(
range: range,
style: .standard,
flash: false,
inactive: index != currentFindMatchIndex,
selectInDocument: index == currentFindMatchIndex
)
}
updateEmphasesForCurrentMatch(emphasisManager: emphasisManager)
}

// Replace all emphases to update state
emphasisManager.replaceEmphases(updatedEmphases, for: EmphasisGroup.find)
func findPanelReplaceButtonClicked() {
guard !findMatches.isEmpty else { return }
replaceCurrentMatch()
}

func findPanelReplaceAllButtonClicked() {
guard !findMatches.isEmpty else { return }
replaceAllMatches()
}

func findPanelUpdateMatchCount(_ count: Int) {
Expand Down
Loading