Skip to content

Commit e7f4ff6

Browse files
committed
Merge branch 'feat/lsp-semantic-highlighter' of https://github.com/thecoolwinter/CodeEdit into feat/lsp-semantic-highlighter
2 parents 59447f4 + 0ce8701 commit e7f4ff6

File tree

5 files changed

+116
-43
lines changed

5 files changed

+116
-43
lines changed

CodeEdit/Features/CEWorkspace/Models/DirectoryEventStream.swift

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -138,43 +138,58 @@ class DirectoryEventStream {
138138

139139
for (index, dictionary) in eventDictionaries.enumerated() {
140140
// Get get file id use dictionary[kFSEventStreamEventExtendedFileIDKey] as? UInt64
141-
guard let path = dictionary[kFSEventStreamEventExtendedDataPathKey] as? String,
142-
let event = getEventFromFlags(eventFlags[index])
141+
guard let path = dictionary[kFSEventStreamEventExtendedDataPathKey] as? String
143142
else {
144143
continue
145144
}
146145

147-
events.append(.init(path: path, eventType: event))
146+
let fsEvents = getEventsFromFlags(eventFlags[index])
147+
148+
for event in fsEvents {
149+
events.append(.init(path: path, eventType: event))
150+
}
148151
}
149152

150153
callback(events)
151154
}
152155

153-
/// Parses an ``FSEvent`` from the raw flag value.
156+
/// Parses ``FSEvent`` from the raw flag value.
157+
///
158+
/// There can be multiple events in the raw flag value,
159+
/// bacause of how OS processes almost simlutaneous actions – thus this functions returns a `Set` of `FSEvent`.
154160
///
155-
/// Often returns ``FSEvent/changeInDirectory`` as `FSEventStream` returns
161+
/// Often returns ``[FSEvent/changeInDirectory]`` as `FSEventStream` returns
156162
/// `kFSEventStreamEventFlagNone (0x00000000)` frequently without more information.
157163
/// - Parameter raw: The int value received from the FSEventStream
158-
/// - Returns: An ``FSEvent`` if a valid one was found, or `nil` otherwise.
159-
func getEventFromFlags(_ raw: FSEventStreamEventFlags) -> FSEvent? {
164+
/// - Returns: A `Set` of ``FSEvent``'s if at least one valid was found, or `[]` otherwise.
165+
private func getEventsFromFlags(_ raw: FSEventStreamEventFlags) -> Set<FSEvent> {
166+
var events: Set<FSEvent> = []
167+
160168
if raw == 0 {
161-
return .changeInDirectory
162-
} else if raw & UInt32(kFSEventStreamEventFlagRootChanged) > 0 {
163-
return .rootChanged
164-
} else if raw & UInt32(kFSEventStreamEventFlagItemChangeOwner) > 0 {
165-
return .itemChangedOwner
166-
} else if raw & UInt32(kFSEventStreamEventFlagItemCreated) > 0 {
167-
return .itemCreated
168-
} else if raw & UInt32(kFSEventStreamEventFlagItemCloned) > 0 {
169-
return .itemCloned
170-
} else if raw & UInt32(kFSEventStreamEventFlagItemModified) > 0 {
171-
return .itemModified
172-
} else if raw & UInt32(kFSEventStreamEventFlagItemRemoved) > 0 {
173-
return .itemRemoved
174-
} else if raw & UInt32(kFSEventStreamEventFlagItemRenamed) > 0 {
175-
return .itemRenamed
176-
} else {
177-
return nil
169+
events.insert(.changeInDirectory)
170+
}
171+
if raw & UInt32(kFSEventStreamEventFlagRootChanged) > 0 {
172+
events.insert(.rootChanged)
173+
}
174+
if raw & UInt32(kFSEventStreamEventFlagItemChangeOwner) > 0 {
175+
events.insert(.itemChangedOwner)
178176
}
177+
if raw & UInt32(kFSEventStreamEventFlagItemCreated) > 0 {
178+
events.insert(.itemCreated)
179+
}
180+
if raw & UInt32(kFSEventStreamEventFlagItemCloned) > 0 {
181+
events.insert(.itemCloned)
182+
}
183+
if raw & UInt32(kFSEventStreamEventFlagItemModified) > 0 {
184+
events.insert(.itemModified)
185+
}
186+
if raw & UInt32(kFSEventStreamEventFlagItemRemoved) > 0 {
187+
events.insert(.itemRemoved)
188+
}
189+
if raw & UInt32(kFSEventStreamEventFlagItemRenamed) > 0 {
190+
events.insert(.itemRenamed)
191+
}
192+
193+
return events
179194
}
180195
}

CodeEdit/Features/Documents/CodeFileDocument/CodeFileDocument.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,23 @@ final class CodeFileDocument: NSDocument, ObservableObject {
188188
NotificationCenter.default.post(name: Self.didCloseNotification, object: fileURL)
189189
}
190190

191+
override func save(_ sender: Any?) {
192+
guard let fileURL else {
193+
super.save(sender)
194+
return
195+
}
196+
197+
do {
198+
// Get parent directory for cases when entire folders were deleted – and recreate them as needed
199+
let directory = fileURL.deletingLastPathComponent()
200+
try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil)
201+
202+
try data(ofType: fileType ?? "").write(to: fileURL, options: .atomic)
203+
} catch {
204+
presentError(error)
205+
}
206+
}
207+
191208
/// Determines the code language of the document.
192209
/// Use ``CodeFileDocument/language`` for the default value before using this. That property is used to override
193210
/// the file's language.

CodeEdit/Features/Editor/TabBar/Tabs/Tab/EditorTabView.swift

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,11 @@ struct EditorTabView: View {
2121
@Environment(\.isFullscreen)
2222
private var isFullscreen
2323

24+
@EnvironmentObject var workspace: WorkspaceDocument
2425
@EnvironmentObject private var editorManager: EditorManager
2526

27+
@StateObject private var fileObserver: EditorTabFileObserver
28+
2629
@AppSettings(\.general.fileIconStyle)
2730
var fileIconStyle
2831

@@ -54,25 +57,25 @@ struct EditorTabView: View {
5457

5558
@EnvironmentObject private var editor: Editor
5659

57-
/// The item associated with the current tab.
60+
/// The file item associated with the current tab.
5861
///
5962
/// You can get tab-related information from here, like `label`, `icon`, etc.
60-
private var item: CEWorkspaceFile
63+
private let tabFile: CEWorkspaceFile
6164

6265
var index: Int
6366

6467
private var isTemporary: Bool {
65-
editor.temporaryTab?.file == item
68+
editor.temporaryTab?.file == tabFile
6669
}
6770

6871
/// Is the current tab the active tab.
6972
private var isActive: Bool {
70-
item == editor.selectedTab?.file
73+
tabFile == editor.selectedTab?.file
7174
}
7275

7376
/// Is the current tab being dragged.
7477
private var isDragging: Bool {
75-
draggingTabId == item.id
78+
draggingTabId == tabFile.id
7679
}
7780

7881
/// Is the current tab being held (by click and hold, not drag).
@@ -86,9 +89,9 @@ struct EditorTabView: View {
8689
private func switchAction() {
8790
// Only set the `selectedId` when they are not equal to avoid performance issue for now.
8891
editorManager.activeEditor = editor
89-
if editor.selectedTab?.file != item {
90-
let tabItem = EditorInstance(file: item)
91-
editor.setSelectedTab(item)
92+
if editor.selectedTab?.file != tabFile {
93+
let tabItem = EditorInstance(file: tabFile)
94+
editor.setSelectedTab(tabFile)
9295
editor.clearFuture()
9396
editor.addToHistory(tabItem)
9497
}
@@ -97,21 +100,22 @@ struct EditorTabView: View {
97100
/// Close the current tab.
98101
func closeAction() {
99102
isAppeared = false
100-
editor.closeTab(file: item)
103+
editor.closeTab(file: tabFile)
101104
}
102105

103106
init(
104-
item: CEWorkspaceFile,
107+
file: CEWorkspaceFile,
105108
index: Int,
106109
draggingTabId: CEWorkspaceFile.ID?,
107110
onDragTabId: CEWorkspaceFile.ID?,
108111
closeButtonGestureActive: Binding<Bool>
109112
) {
110-
self.item = item
113+
self.tabFile = file
111114
self.index = index
112115
self.draggingTabId = draggingTabId
113116
self.onDragTabId = onDragTabId
114117
self._closeButtonGestureActive = closeButtonGestureActive
118+
self._fileObserver = StateObject(wrappedValue: EditorTabFileObserver(file: file))
115119
}
116120

117121
@ViewBuilder var content: some View {
@@ -122,26 +126,27 @@ struct EditorTabView: View {
122126
)
123127
// Tab content (icon and text).
124128
HStack(alignment: .center, spacing: 3) {
125-
Image(nsImage: item.nsIcon)
129+
Image(nsImage: tabFile.nsIcon)
126130
.frame(width: 16, height: 16)
127131
.foregroundColor(
128132
fileIconStyle == .color
129133
&& activeState != .inactive && isActiveEditor
130-
? item.iconColor
134+
? tabFile.iconColor
131135
: .secondary
132136
)
133-
Text(item.name)
137+
Text(tabFile.name)
134138
.font(
135139
isTemporary
136140
? .system(size: 11.0).italic()
137141
: .system(size: 11.0)
138142
)
139143
.lineLimit(1)
144+
.strikethrough(fileObserver.isDeleted, color: .primary)
140145
}
141146
.frame(maxHeight: .infinity) // To max-out the parent (tab bar) area.
142147
.accessibilityElement(children: .ignore)
143148
.accessibilityAddTraits(.isStaticText)
144-
.accessibilityLabel(item.name)
149+
.accessibilityLabel(tabFile.name)
145150
.padding(.horizontal, 20)
146151
.overlay {
147152
ZStack {
@@ -152,7 +157,7 @@ struct EditorTabView: View {
152157
isDragging: draggingTabId != nil || onDragTabId != nil,
153158
closeAction: closeAction,
154159
closeButtonGestureActive: $closeButtonGestureActive,
155-
item: item,
160+
item: tabFile,
156161
isHoveringClose: $isHoveringClose
157162
)
158163
}
@@ -227,8 +232,14 @@ struct EditorTabView: View {
227232
}
228233
)
229234
.zIndex(isActive ? 2 : (isDragging ? 3 : (isPressing ? 1 : 0)))
230-
.id(item.id)
231-
.tabBarContextMenu(item: item, isTemporary: isTemporary)
235+
.id(tabFile.id)
236+
.tabBarContextMenu(item: tabFile, isTemporary: isTemporary)
232237
.accessibilityElement(children: .contain)
238+
.onAppear {
239+
workspace.workspaceFileManager?.addObserver(fileObserver)
240+
}
241+
.onDisappear {
242+
workspace.workspaceFileManager?.removeObserver(fileObserver)
243+
}
233244
}
234245
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//
2+
// EditorTabFileObserver.swift
3+
// CodeEdit
4+
//
5+
// Created by Filipp Kuznetsov on 25.02.2025.
6+
//
7+
8+
import Foundation
9+
import SwiftUI
10+
11+
/// Observer ViewModel for tracking file deletion
12+
@MainActor
13+
final class EditorTabFileObserver: ObservableObject,
14+
CEWorkspaceFileManagerObserver {
15+
@Published private(set) var isDeleted = false
16+
17+
private let tabFile: CEWorkspaceFile
18+
19+
init(file: CEWorkspaceFile) {
20+
self.tabFile = file
21+
}
22+
23+
nonisolated func fileManagerUpdated(updatedItems: Set<CEWorkspaceFile>) {
24+
Task { @MainActor in
25+
if let parent = tabFile.parent, updatedItems.contains(parent) {
26+
isDeleted = tabFile.doesExist == false
27+
}
28+
}
29+
}
30+
}

CodeEdit/Features/Editor/TabBar/Tabs/Views/EditorTabs.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ struct EditorTabs: View {
261261
ForEach(Array(openedTabs.enumerated()), id: \.element) { index, id in
262262
if let item = editor.tabs.first(where: { $0.file.id == id }) {
263263
EditorTabView(
264-
item: item.file,
264+
file: item.file,
265265
index: index,
266266
draggingTabId: draggingTabId,
267267
onDragTabId: onDragTabId,

0 commit comments

Comments
 (0)