Skip to content

Commit f444927

Browse files
tom-ludwigthecoolwinteraustincondiff
authored
feat: Find in Editor (CodeEditApp#295)
> [!IMPORTANT] > ~~We need to merge CodeEditApp/CodeEditTextView#78 before merging this PR.~~ ### Description This PR introduces the initial implementation of the “Find in Editor” feature for the source editor. Users can now search for text within the currently open file using ⌘ F. All matching results are visually emphasized, and users can navigate between matches using next/previous controls. What’s Included - Text search across the current document - Match highlighting with emphasis on the currently selected match - Keyboard shortcut support: ⌘ F to activate the find bar - Looping navigation with HUD notifications: - Reaching the end → loops to first result (arrow.triangle.capsulepath) - Reaching the beginning → loops to last result (flipped arrow.triangle.capsulepath) - No more matches → arrow.down.to.line HUD icon displayed ### Related Issues - CodeEditApp/CodeEditTextView#1 - closes CodeEditApp/CodeEditTextView#3 - CodeEditApp/CodeEditTextView#78 * #ISSUE_NUMBER ### Checklist <!--- Add things that are not yet implemented above --> - [x] I read and understood the [contributing guide](https://github.com/CodeEditApp/CodeEdit/blob/main/CONTRIBUTING.md) as well as the [code of conduct](https://github.com/CodeEditApp/CodeEdit/blob/main/CODE_OF_CONDUCT.md) - [x] The issues this PR addresses are related to each other - [x] My changes generate no new warnings - [x] My code builds and runs on my machine - [x] My changes are all related to the related issue above - [x] I documented my code ### Screenshots <!--- REQUIRED: if issue is UI related --> <!--- IMPORTANT: Fill out all required fields. Otherwise we might close this PR temporarily --> --------- Co-authored-by: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Co-authored-by: Austin Condiff <austin.condiff@gmail.com>
1 parent 59d8cb4 commit f444927

38 files changed

+2071
-393
lines changed

Example/CodeEditSourceEditorExample/CodeEditSourceEditorExample.xcodeproj/project.pbxproj

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
/* Begin PBXBuildFile section */
1010
61621C612C74FB2200494A4A /* CodeEditSourceEditor in Frameworks */ = {isa = PBXBuildFile; productRef = 61621C602C74FB2200494A4A /* CodeEditSourceEditor */; };
11+
61CE772F2D19BF7D00908C57 /* CodeEditSourceEditor in Frameworks */ = {isa = PBXBuildFile; productRef = 61CE772E2D19BF7D00908C57 /* CodeEditSourceEditor */; };
12+
61CE77322D19BFAA00908C57 /* CodeEditSourceEditor in Frameworks */ = {isa = PBXBuildFile; productRef = 61CE77312D19BFAA00908C57 /* CodeEditSourceEditor */; };
1113
6C13652E2B8A7B94004A1D18 /* CodeEditSourceEditorExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C13652D2B8A7B94004A1D18 /* CodeEditSourceEditorExampleApp.swift */; };
1214
6C1365302B8A7B94004A1D18 /* CodeEditSourceEditorExampleDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C13652F2B8A7B94004A1D18 /* CodeEditSourceEditorExampleDocument.swift */; };
1315
6C1365322B8A7B94004A1D18 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C1365312B8A7B94004A1D18 /* ContentView.swift */; };
@@ -41,6 +43,8 @@
4143
buildActionMask = 2147483647;
4244
files = (
4345
61621C612C74FB2200494A4A /* CodeEditSourceEditor in Frameworks */,
46+
61CE772F2D19BF7D00908C57 /* CodeEditSourceEditor in Frameworks */,
47+
61CE77322D19BFAA00908C57 /* CodeEditSourceEditor in Frameworks */,
4448
);
4549
runOnlyForDeploymentPostprocessing = 0;
4650
};
@@ -140,6 +144,8 @@
140144
name = CodeEditSourceEditorExample;
141145
packageProductDependencies = (
142146
61621C602C74FB2200494A4A /* CodeEditSourceEditor */,
147+
61CE772E2D19BF7D00908C57 /* CodeEditSourceEditor */,
148+
61CE77312D19BFAA00908C57 /* CodeEditSourceEditor */,
143149
);
144150
productName = CodeEditSourceEditorExample;
145151
productReference = 6C13652A2B8A7B94004A1D18 /* CodeEditSourceEditorExample.app */;
@@ -412,6 +418,14 @@
412418
isa = XCSwiftPackageProductDependency;
413419
productName = CodeEditSourceEditor;
414420
};
421+
61CE772E2D19BF7D00908C57 /* CodeEditSourceEditor */ = {
422+
isa = XCSwiftPackageProductDependency;
423+
productName = CodeEditSourceEditor;
424+
};
425+
61CE77312D19BFAA00908C57 /* CodeEditSourceEditor */ = {
426+
isa = XCSwiftPackageProductDependency;
427+
productName = CodeEditSourceEditor;
428+
};
415429
/* End XCSwiftPackageProductDependency section */
416430
};
417431
rootObject = 6C1365222B8A7B94004A1D18 /* Project object */;

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

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

Example/CodeEditSourceEditorExample/CodeEditSourceEditorExample/CodeEditSourceEditorExampleApp.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@ struct CodeEditSourceEditorExampleApp: App {
1313
DocumentGroup(newDocument: CodeEditSourceEditorExampleDocument()) { file in
1414
ContentView(document: file.$document, fileURL: file.fileURL)
1515
}
16+
.windowToolbarStyle(.unifiedCompact)
1617
}
1718
}

Example/CodeEditSourceEditorExample/CodeEditSourceEditorExample/Extensions/EditorTheme+Default.swift

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import AppKit
1010
import CodeEditSourceEditor
1111

1212
extension EditorTheme {
13-
static var standard: EditorTheme {
13+
static var light: EditorTheme {
1414
EditorTheme(
1515
text: Attribute(color: NSColor(hex: "000000")),
1616
insertionPoint: NSColor(hex: "000000"),
@@ -25,9 +25,29 @@ extension EditorTheme {
2525
variables: Attribute(color: NSColor(hex: "0F68A0")),
2626
values: Attribute(color: NSColor(hex: "6C36A9")),
2727
numbers: Attribute(color: NSColor(hex: "1C00CF")),
28-
strings: Attribute(color: NSColor(hex: "C41A16"), bold: true, italic: true),
28+
strings: Attribute(color: NSColor(hex: "C41A16")),
2929
characters: Attribute(color: NSColor(hex: "1C00CF")),
30-
comments: Attribute(color: NSColor(hex: "267507"), italic: true)
30+
comments: Attribute(color: NSColor(hex: "267507"))
31+
)
32+
}
33+
static var dark: EditorTheme {
34+
EditorTheme(
35+
text: Attribute(color: NSColor(hex: "FFFFFF")),
36+
insertionPoint: NSColor(hex: "007AFF"),
37+
invisibles: Attribute(color: NSColor(hex: "53606E")),
38+
background: NSColor(hex: "292A30"),
39+
lineHighlight: NSColor(hex: "2F3239"),
40+
selection: NSColor(hex: "646F83"),
41+
keywords: Attribute(color: NSColor(hex: "FF7AB2"), bold: true),
42+
commands: Attribute(color: NSColor(hex: "78C2B3")),
43+
types: Attribute(color: NSColor(hex: "6BDFFF")),
44+
attributes: Attribute(color: NSColor(hex: "CC9768")),
45+
variables: Attribute(color: NSColor(hex: "4EB0CC")),
46+
values: Attribute(color: NSColor(hex: "B281EB")),
47+
numbers: Attribute(color: NSColor(hex: "D9C97C")),
48+
strings: Attribute(color: NSColor(hex: "FF8170")),
49+
characters: Attribute(color: NSColor(hex: "D9C97C")),
50+
comments: Attribute(color: NSColor(hex: "7F8C98"))
3151
)
3252
}
3353
}

Example/CodeEditSourceEditorExample/CodeEditSourceEditorExample/Views/ContentView.swift

Lines changed: 96 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -11,82 +11,121 @@ import CodeEditLanguages
1111
import CodeEditTextView
1212

1313
struct ContentView: View {
14+
@Environment(\.colorScheme)
15+
var colorScheme
16+
1417
@Binding var document: CodeEditSourceEditorExampleDocument
1518
let fileURL: URL?
1619

1720
@State private var language: CodeLanguage = .default
18-
@State private var theme: EditorTheme = .standard
19-
@State private var font: NSFont = NSFont.monospacedSystemFont(ofSize: 12, weight: .regular)
21+
@State private var theme: EditorTheme = .light
22+
@State private var font: NSFont = NSFont.monospacedSystemFont(ofSize: 12, weight: .medium)
2023
@AppStorage("wrapLines") private var wrapLines: Bool = true
21-
@State private var cursorPositions: [CursorPosition] = []
24+
@State private var cursorPositions: [CursorPosition] = [.init(line: 1, column: 1)]
2225
@AppStorage("systemCursor") private var useSystemCursor: Bool = false
2326
@State private var isInLongParse = false
27+
@State private var settingsIsPresented: Bool = false
28+
@State private var treeSitterClient = TreeSitterClient()
2429

2530
init(document: Binding<CodeEditSourceEditorExampleDocument>, fileURL: URL?) {
2631
self._document = document
2732
self.fileURL = fileURL
2833
}
2934

3035
var body: some View {
31-
VStack(spacing: 0) {
32-
HStack {
33-
Text("Language")
34-
LanguagePicker(language: $language)
35-
.frame(maxWidth: 100)
36-
Toggle("Wrap Lines", isOn: $wrapLines)
37-
if #available(macOS 14, *) {
38-
Toggle("Use System Cursor", isOn: $useSystemCursor)
39-
} else {
40-
Toggle("Use System Cursor", isOn: $useSystemCursor)
41-
.disabled(true)
42-
.help("macOS 14 required")
36+
GeometryReader { proxy in
37+
CodeEditSourceEditor(
38+
$document.text,
39+
language: language,
40+
theme: theme,
41+
font: font,
42+
tabWidth: 4,
43+
lineHeight: 1.2,
44+
wrapLines: wrapLines,
45+
cursorPositions: $cursorPositions,
46+
useThemeBackground: true,
47+
highlightProviders: [treeSitterClient],
48+
contentInsets: NSEdgeInsets(top: proxy.safeAreaInsets.top, left: 0, bottom: 28.0, right: 0),
49+
useSystemCursor: useSystemCursor
50+
)
51+
.overlay(alignment: .bottom) {
52+
HStack {
53+
Menu {
54+
Toggle("Wrap Lines", isOn: $wrapLines)
55+
if #available(macOS 14, *) {
56+
Toggle("Use System Cursor", isOn: $useSystemCursor)
57+
} else {
58+
Toggle("Use System Cursor", isOn: $useSystemCursor)
59+
.disabled(true)
60+
.help("macOS 14 required")
61+
}
62+
} label: {}
63+
.background {
64+
Image(systemName: "switch.2")
65+
.foregroundStyle(.secondary)
66+
.font(.system(size: 13.5, weight: .regular))
67+
}
68+
.menuStyle(.borderlessButton)
69+
.menuIndicator(.hidden)
70+
.frame(maxWidth: 18, alignment: .center)
71+
Spacer()
72+
Group {
73+
if isInLongParse {
74+
HStack(spacing: 5) {
75+
ProgressView()
76+
.controlSize(.small)
77+
Text("Parsing Document")
78+
}
79+
} else {
80+
Text(getLabel(cursorPositions))
81+
}
82+
}
83+
.foregroundStyle(.secondary)
84+
Divider()
85+
.frame(height: 12)
86+
LanguagePicker(language: $language)
87+
.buttonStyle(.borderless)
4388
}
44-
Spacer()
45-
Text(getLabel(cursorPositions))
46-
}
47-
.padding(4)
48-
.zIndex(2)
49-
.background(Color(NSColor.windowBackgroundColor))
50-
Divider()
51-
ZStack {
52-
if isInLongParse {
89+
.font(.subheadline)
90+
.fontWeight(.medium)
91+
.controlSize(.small)
92+
.padding(.horizontal, 8)
93+
.frame(height: 28)
94+
.background(.bar)
95+
.overlay(alignment: .top) {
5396
VStack {
54-
HStack {
55-
Spacer()
56-
Text("Parsing document...")
57-
Spacer()
58-
}
59-
.padding(4)
60-
.background(Color(NSColor.windowBackgroundColor))
61-
Spacer()
97+
Divider()
98+
.overlay {
99+
if colorScheme == .dark {
100+
Color.black
101+
}
102+
}
62103
}
63-
.zIndex(2)
64-
.transition(.opacity)
65104
}
66-
CodeEditSourceEditor(
67-
$document.text,
68-
language: language,
69-
theme: theme,
70-
font: font,
71-
tabWidth: 4,
72-
lineHeight: 1.2,
73-
wrapLines: wrapLines,
74-
cursorPositions: $cursorPositions,
75-
useSystemCursor: useSystemCursor
76-
)
105+
.zIndex(2)
106+
.onAppear {
107+
self.language = detectLanguage(fileURL: fileURL) ?? .default
108+
self.theme = colorScheme == .dark ? .dark : .light
109+
}
77110
}
78-
}
79-
.onAppear {
80-
self.language = detectLanguage(fileURL: fileURL) ?? .default
81-
}
82-
.onReceive(NotificationCenter.default.publisher(for: TreeSitterClient.Constants.longParse)) { _ in
83-
withAnimation(.easeIn(duration: 0.1)) {
84-
isInLongParse = true
111+
.ignoresSafeArea()
112+
.frame(maxWidth: .infinity, maxHeight: .infinity)
113+
.onReceive(NotificationCenter.default.publisher(for: TreeSitterClient.Constants.longParse)) { _ in
114+
withAnimation(.easeIn(duration: 0.1)) {
115+
isInLongParse = true
116+
}
85117
}
86-
}
87-
.onReceive(NotificationCenter.default.publisher(for: TreeSitterClient.Constants.longParseFinished)) { _ in
88-
withAnimation(.easeIn(duration: 0.1)) {
89-
isInLongParse = false
118+
.onReceive(NotificationCenter.default.publisher(for: TreeSitterClient.Constants.longParseFinished)) { _ in
119+
withAnimation(.easeIn(duration: 0.1)) {
120+
isInLongParse = false
121+
}
122+
}
123+
.onChange(of: colorScheme) { _, newValue in
124+
if newValue == .dark {
125+
theme = .dark
126+
} else {
127+
theme = .light
128+
}
90129
}
91130
}
92131
}
@@ -105,7 +144,7 @@ struct ContentView: View {
105144
/// - Returns: A string describing the user's location in a document.
106145
func getLabel(_ cursorPositions: [CursorPosition]) -> String {
107146
if cursorPositions.isEmpty {
108-
return ""
147+
return "No cursor"
109148
}
110149

111150
// More than one selection, display the number of selections.

Package.resolved

Lines changed: 4 additions & 4 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.8.1"
20+
from: "0.8.2"
2121
),
2222
// tree-sitter languages
2323
.package(

0 commit comments

Comments
 (0)