Skip to content

Commit d920eb6

Browse files
committed
Add Search Container, Search Bar, Show/Hide Commands, Animations
1 parent a10ba6f commit d920eb6

File tree

10 files changed

+392
-207
lines changed

10 files changed

+392
-207
lines changed

Example/CodeEditSourceEditorExample/CodeEditSourceEditorExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 6 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.resolved

Lines changed: 8 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ let package = Package(
1717
// A fast, efficient, text view for code.
1818
.package(
1919
url: "https://github.com/CodeEditApp/CodeEditTextView.git",
20-
from: "0.7.6"
20+
from: "0.7.9"
2121
),
2222
// tree-sitter languages
2323
.package(

Sources/CodeEditSourceEditor/Controller/TextViewController+LoadView.swift

Lines changed: 27 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,7 @@ import AppKit
1111
extension TextViewController {
1212
// swiftlint:disable:next function_body_length
1313
override public func loadView() {
14-
let stackView = NSStackView()
15-
stackView.orientation = .vertical
16-
stackView.spacing = 10
17-
stackView.alignment = .leading
18-
stackView.translatesAutoresizingMaskIntoConstraints = false
14+
super.loadView()
1915

2016
scrollView = NSScrollView()
2117
textView.postsFrameChangedNotifications = true
@@ -39,46 +35,12 @@ extension TextViewController {
3935
for: .horizontal
4036
)
4137

42-
searchField = NSTextField()
43-
searchField.placeholderString = "Search..."
44-
searchField.controlSize = .regular // TODO: a
45-
searchField.focusRingType = .none
46-
searchField.bezelStyle = .roundedBezel
47-
searchField.drawsBackground = true
48-
searchField.translatesAutoresizingMaskIntoConstraints = false
49-
searchField.action = #selector(onSubmit)
50-
searchField.target = self
51-
52-
prevButton = NSButton(title: "◀︎", target: self, action: #selector(prevButtonClicked))
53-
prevButton.bezelStyle = .texturedRounded
54-
prevButton.controlSize = .small
55-
prevButton.translatesAutoresizingMaskIntoConstraints = false
56-
57-
nextButton = NSButton(title: "▶︎", target: self, action: #selector(nextButtonClicked))
58-
nextButton.bezelStyle = .texturedRounded
59-
nextButton.controlSize = .small
60-
nextButton.translatesAutoresizingMaskIntoConstraints = false
61-
62-
stackview = NSStackView()
63-
stackview.orientation = .horizontal
64-
stackview.spacing = 8
65-
stackview.edgeInsets = NSEdgeInsets(top: 5, left: 10, bottom: 5, right: 10)
66-
stackview.translatesAutoresizingMaskIntoConstraints = false
67-
68-
stackview.addView(searchField, in: .leading)
69-
stackview.addView(prevButton, in: .trailing)
70-
stackview.addView(nextButton, in: .trailing)
38+
let searchController = SearchViewController(target: self, childView: scrollView)
39+
addChild(searchController)
40+
self.view.addSubview(searchController.view)
41+
searchController.view.viewDidMoveToSuperview()
42+
self.searchController = searchController
7143

72-
NotificationCenter.default.addObserver(
73-
self,
74-
selector: #selector(searchFieldUpdated(_:)),
75-
name: NSControl.textDidChangeNotification,
76-
object: searchField
77-
)
78-
79-
stackView.addArrangedSubview(stackview)
80-
stackView.addArrangedSubview(scrollView)
81-
self.view = stackView
8244
if let _undoManager {
8345
textView.setUndoManager(_undoManager)
8446
}
@@ -90,15 +52,10 @@ extension TextViewController {
9052
setUpTextFormation()
9153

9254
NSLayoutConstraint.activate([
93-
stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
94-
stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
95-
stackView.topAnchor.constraint(equalTo: view.topAnchor),
96-
stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
97-
98-
// scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
99-
// scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
100-
// scrollView.topAnchor.constraint(equalTo: view.topAnchor),
101-
// scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
55+
searchController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
56+
searchController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
57+
searchController.view.topAnchor.constraint(equalTo: view.topAnchor),
58+
searchController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor)
10259
])
10360

10461
if !cursorPositions.isEmpty {
@@ -162,14 +119,26 @@ extension TextViewController {
162119
if let localEventMonitor = self.localEvenMonitor {
163120
NSEvent.removeMonitor(localEventMonitor)
164121
}
165-
self.localEvenMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [weak self] event in
122+
self.localEvenMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [weak self] event -> NSEvent? in
166123
guard self?.view.window?.firstResponder == self?.textView else { return event }
167-
let commandKey = NSEvent.ModifierFlags.command.rawValue
168-
let modifierFlags = event.modifierFlags.intersection(.deviceIndependentFlagsMask).rawValue
169-
if modifierFlags == commandKey && event.charactersIgnoringModifiers == "/" {
124+
let modifierFlags = event.modifierFlags.intersection(.deviceIndependentFlagsMask)
125+
126+
switch (modifierFlags, event.charactersIgnoringModifiers?.lowercased()) {
127+
case (.command, "/"):
170128
self?.handleCommandSlash()
171129
return nil
172-
} else {
130+
case (.command, "f"):
131+
_ = self?.textView.resignFirstResponder()
132+
self?.searchController?.showSearchBar()
133+
return nil
134+
case ([], "\u{1b}"): // Escape key
135+
self?.searchController?.hideSearchBar()
136+
_ = self?.textView.becomeFirstResponder()
137+
self?.textView.selectionManager.setSelectedRanges(
138+
self?.textView.selectionManager.textSelections.map { $0.range } ?? []
139+
)
140+
return nil
141+
default:
173142
return event
174143
}
175144
}

Sources/CodeEditSourceEditor/Controller/TextViewController.swift

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,9 @@ public class TextViewController: NSViewController {
2020
// swiftlint:disable:next line_length
2121
public static let cursorPositionUpdatedNotification: Notification.Name = .init("TextViewController.cursorPositionNotification")
2222

23+
weak var searchController: SearchViewController?
24+
2325
var scrollView: NSScrollView!
24-
// SEARCH
25-
var stackview: NSStackView!
26-
var searchField: NSTextField!
27-
var prevButton: NSButton!
28-
var nextButton: NSButton!
29-
// SEARCH
3026
var textView: TextView!
3127
var gutterView: GutterView!
3228
internal var _undoManager: CEUndoManager!
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
//
2+
// SearchBar.swift
3+
// CodeEditSourceEditor
4+
//
5+
// Created by Khan Winter on 3/10/25.
6+
//
7+
8+
import AppKit
9+
10+
protocol SearchBarDelegate: AnyObject {
11+
func searchBarOnSubmit()
12+
func searchBarOnCancel()
13+
func searchBarDidUpdate(_ searchText: String)
14+
func searchBarPrevButtonClicked()
15+
func searchBarNextButtonClicked()
16+
}
17+
18+
/// A control for searching a document and navigating results.
19+
final class SearchBar: NSStackView {
20+
weak var searchDelegate: SearchBarDelegate?
21+
22+
var searchField: NSTextField!
23+
var prevButton: NSButton!
24+
var nextButton: NSButton!
25+
26+
init(delegate: SearchBarDelegate?) {
27+
super.init(frame: .zero)
28+
29+
self.searchDelegate = delegate
30+
31+
searchField = NSTextField()
32+
searchField.placeholderString = "Search..."
33+
searchField.controlSize = .regular // TODO: a
34+
searchField.focusRingType = .none
35+
searchField.bezelStyle = .roundedBezel
36+
searchField.drawsBackground = true
37+
searchField.translatesAutoresizingMaskIntoConstraints = false
38+
searchField.action = #selector(onSubmit)
39+
searchField.target = self
40+
41+
prevButton = NSButton(title: "◀︎", target: self, action: #selector(prevButtonClicked))
42+
prevButton.bezelStyle = .texturedRounded
43+
prevButton.controlSize = .small
44+
prevButton.translatesAutoresizingMaskIntoConstraints = false
45+
46+
nextButton = NSButton(title: "▶︎", target: self, action: #selector(nextButtonClicked))
47+
nextButton.bezelStyle = .texturedRounded
48+
nextButton.controlSize = .small
49+
nextButton.translatesAutoresizingMaskIntoConstraints = false
50+
51+
self.orientation = .horizontal
52+
self.spacing = 8
53+
self.edgeInsets = NSEdgeInsets(top: 5, left: 10, bottom: 5, right: 10)
54+
self.translatesAutoresizingMaskIntoConstraints = false
55+
56+
self.addView(searchField, in: .leading)
57+
self.addView(prevButton, in: .trailing)
58+
self.addView(nextButton, in: .trailing)
59+
60+
NotificationCenter.default.addObserver(
61+
self,
62+
selector: #selector(searchFieldUpdated(_:)),
63+
name: NSControl.textDidChangeNotification,
64+
object: searchField
65+
)
66+
}
67+
68+
required init?(coder: NSCoder) {
69+
fatalError("init(coder:) has not been implemented")
70+
}
71+
72+
/// Hide the search bar when escape is pressed
73+
override func cancelOperation(_ sender: Any?) {
74+
searchDelegate?.searchBarOnCancel()
75+
}
76+
77+
// MARK: - Delegate Messaging
78+
79+
@objc func searchFieldUpdated(_ notification: Notification) {
80+
guard let searchField = notification.object as? NSTextField else { return }
81+
searchDelegate?.searchBarDidUpdate(searchField.stringValue)
82+
}
83+
84+
@objc func onSubmit() {
85+
searchDelegate?.searchBarOnSubmit()
86+
}
87+
88+
@objc func prevButtonClicked() {
89+
searchDelegate?.searchBarPrevButtonClicked()
90+
}
91+
92+
@objc func nextButtonClicked() {
93+
searchDelegate?.searchBarNextButtonClicked()
94+
}
95+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//
2+
// SearchTarget.swift
3+
// CodeEditSourceEditor
4+
//
5+
// Created by Khan Winter on 3/10/25.
6+
//
7+
8+
// This dependency is not ideal, maybe we could make this another protocol that the emphasize API conforms to similar
9+
// to this one?
10+
import CodeEditTextView
11+
12+
protocol SearchTarget: AnyObject {
13+
var emphasizeAPI: EmphasizeAPI? { get }
14+
var text: String { get }
15+
16+
var cursorPositions: [CursorPosition] { get }
17+
func setCursorPositions(_ positions: [CursorPosition])
18+
func updateCursorPosition()
19+
}

0 commit comments

Comments
 (0)