Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix Subtle FileManager Bug, Fix Duplicate File Reads
Browse files Browse the repository at this point in the history
thecoolwinter committed Sep 17, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent 3127468 commit f7e0253
Showing 8 changed files with 85 additions and 90 deletions.
Original file line number Diff line number Diff line change
@@ -71,7 +71,6 @@ extension CEWorkspaceFileManager {
}
}

var largestValue = 0
fileExtension = fileExtensions.sorted(by: { $0.value > $1.value }).first?.key ?? "txt"
}

Original file line number Diff line number Diff line change
@@ -189,8 +189,9 @@ final class CEWorkspaceFileManager {
/// - Parameter file: The parent element.
/// - Returns: A child element with an associated parent.
func createChild(_ url: URL, forParent file: CEWorkspaceFile) -> CEWorkspaceFile {
let childId = URL(filePath: file.id).appendingPathComponent(url.lastPathComponent).relativePath
let newFileItem = CEWorkspaceFile(id: childId, url: url)
let relativeURL = URL(filePath: file.id).appendingPathComponent(url.lastPathComponent)
let childId = relativeURL.relativePath
let newFileItem = CEWorkspaceFile(id: childId, url: relativeURL)
newFileItem.parent = file
return newFileItem
}
Original file line number Diff line number Diff line change
@@ -194,4 +194,17 @@ final class CodeFileDocument: NSDocument, ObservableObject {
suffixBuffer: content?.string.getLastLines(5)
)
}

func findWorkspace() -> WorkspaceDocument? {
CodeEditDocumentController.shared.documents.first(where: { doc in
guard let workspace = doc as? WorkspaceDocument, let path = self.languageServerURI else { return false }
// createIfNotFound is safe here because it will still exit if the file and the workspace
// do not share a path prefix
return workspace
.workspaceFileManager?
.getFile(path, createIfNotFound: true)?
.fileDocument?
.isEqual(self) ?? false
}) as? WorkspaceDocument
}
}
1 change: 0 additions & 1 deletion CodeEdit/Features/Editor/Models/Editor.swift
Original file line number Diff line number Diff line change
@@ -216,7 +216,6 @@ final class Editor: ObservableObject, Identifiable {
let contentType = item.file.resolvedURL.contentType
let codeFile = try CodeFileDocument(
for: item.file.url,
// TODO: FILE CONTENTS ARE READ MULTIPLE TIMES
withContentsOf: item.file.resolvedURL,
ofType: contentType?.identifier ?? ""
)
25 changes: 8 additions & 17 deletions CodeEdit/Features/Editor/Views/EditorAreaView.swift
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@
//

import SwiftUI
import CodeEditTextView

struct EditorAreaView: View {
@AppSettings(\.general.showEditorPathBar)
@@ -25,6 +26,12 @@ struct EditorAreaView: View {

@State var codeFile: CodeFileDocument?

init(editor: Editor, focus: FocusState<Editor?>.Binding) {
self.editor = editor
self._focus = focus
self.codeFile = editor.selectedTab?.file.fileDocument
}

var body: some View {
var shouldShowTabBar: Bool {
return navigationStyle == .openInTabs
@@ -54,22 +61,6 @@ struct EditorAreaView: View {
.opacity(dimEditorsWithoutFocus && editor != editorManager.activeEditor ? 0.5 : 1)
} else {
LoadingFileView(selected.file.name)
.task {
do {
let contentType = selected.file.resolvedURL.contentType
let newCodeFile = try CodeFileDocument(
for: selected.file.url,
withContentsOf: selected.file.resolvedURL,
ofType: contentType?.identifier ?? ""
)

selected.file.fileDocument = newCodeFile
CodeEditDocumentController.shared.addDocument(newCodeFile)
self.codeFile = newCodeFile
} catch {
print(error.localizedDescription)
}
}
}

} else {
@@ -108,7 +99,7 @@ struct EditorAreaView: View {
.background(EffectView(.headerView))
}
.focused($focus, equals: editor)
.onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("CodeEditor.didBeginEditing"))) { _ in
.onReceive(NotificationCenter.default.publisher(for: TextView.textDidChangeNotification)) { _ in
if navigationStyle == .openInTabs {
editor.temporaryTab = nil
}
104 changes: 53 additions & 51 deletions CodeEdit/Features/LSP/Service/LSPService+Events.swift
Original file line number Diff line number Diff line change
@@ -32,62 +32,64 @@ extension LSPService {
}

private func handleEvent(_ event: ServerEvent, for key: ClientKey) {
switch event {
case let .request(id, request):
print("Request ID: \(id) for \(key.languageId.rawValue)")
handleRequest(request)
case let .notification(notification):
handleNotification(notification)
case let .error(error):
print("Error from EventStream for \(key.languageId.rawValue): \(error)")
}
// TODO: Handle Events
// switch event {
// case let .request(id, request):
// print("Request ID: \(id) for \(key.languageId.rawValue)")
// handleRequest(request)
// case let .notification(notification):
// handleNotification(notification)
// case let .error(error):
// print("Error from EventStream for \(key.languageId.rawValue): \(error)")
// }
}

// swiftlint:disable:next cyclomatic_complexity
private func handleRequest(_ request: ServerRequest) {
switch request {
case let .workspaceConfiguration(params, _):
print("workspaceConfiguration: \(params)")
case let .workspaceFolders(handler):
print("workspaceFolders: \(String(describing: handler))")
case let .workspaceApplyEdit(params, _):
print("workspaceApplyEdit: \(params)")
case let .clientRegisterCapability(params, _):
print("clientRegisterCapability: \(params)")
case let .clientUnregisterCapability(params, _):
print("clientUnregisterCapability: \(params)")
case let .workspaceCodeLensRefresh(handler):
print("workspaceCodeLensRefresh: \(String(describing: handler))")
case let .workspaceSemanticTokenRefresh(handler):
print("workspaceSemanticTokenRefresh: \(String(describing: handler))")
case let .windowShowMessageRequest(params, _):
print("windowShowMessageRequest: \(params)")
case let .windowShowDocument(params, _):
print("windowShowDocument: \(params)")
case let .windowWorkDoneProgressCreate(params, _):
print("windowWorkDoneProgressCreate: \(params)")

default:
print()
}
// TODO: Handle Requests
// switch request {
// case let .workspaceConfiguration(params, _):
// print("workspaceConfiguration: \(params)")
// case let .workspaceFolders(handler):
// print("workspaceFolders: \(String(describing: handler))")
// case let .workspaceApplyEdit(params, _):
// print("workspaceApplyEdit: \(params)")
// case let .clientRegisterCapability(params, _):
// print("clientRegisterCapability: \(params)")
// case let .clientUnregisterCapability(params, _):
// print("clientUnregisterCapability: \(params)")
// case let .workspaceCodeLensRefresh(handler):
// print("workspaceCodeLensRefresh: \(String(describing: handler))")
// case let .workspaceSemanticTokenRefresh(handler):
// print("workspaceSemanticTokenRefresh: \(String(describing: handler))")
// case let .windowShowMessageRequest(params, _):
// print("windowShowMessageRequest: \(params)")
// case let .windowShowDocument(params, _):
// print("windowShowDocument: \(params)")
// case let .windowWorkDoneProgressCreate(params, _):
// print("windowWorkDoneProgressCreate: \(params)")
//
// default:
// print()
// }
}

private func handleNotification(_ notification: ServerNotification) {
switch notification {
case let .windowLogMessage(params):
print("windowLogMessage \(params.type)\n```\n\(params.message)\n```\n")
case let .windowShowMessage(params):
print("windowShowMessage \(params.type)\n```\n\(params.message)\n```\n")
case let .textDocumentPublishDiagnostics(params):
print("textDocumentPublishDiagnostics: \(params)")
case let .telemetryEvent(params):
print("telemetryEvent: \(params)")
case let .protocolCancelRequest(params):
print("protocolCancelRequest: \(params)")
case let .protocolProgress(params):
print("protocolProgress: \(params)")
case let .protocolLogTrace(params):
print("protocolLogTrace: \(params)")
}
// TODO: Handle Notifications
// switch notification {
// case let .windowLogMessage(params):
// print("windowLogMessage \(params.type)\n```\n\(params.message)\n```\n")
// case let .windowShowMessage(params):
// print("windowShowMessage \(params.type)\n```\n\(params.message)\n```\n")
// case let .textDocumentPublishDiagnostics(params):
// print("textDocumentPublishDiagnostics: \(params)")
// case let .telemetryEvent(params):
// print("telemetryEvent: \(params)")
// case let .protocolCancelRequest(params):
// print("protocolCancelRequest: \(params)")
// case let .protocolProgress(params):
// print("protocolProgress: \(params)")
// case let .protocolLogTrace(params):
// print("protocolLogTrace: \(params)")
// }
}
}
19 changes: 5 additions & 14 deletions CodeEdit/Features/LSP/Service/LSPService.swift
Original file line number Diff line number Diff line change
@@ -12,15 +12,6 @@ import LanguageClient
import LanguageServerProtocol
import CodeEditLanguages

extension CodeFileDocument {
func findWorkspace() -> WorkspaceDocument? {
CodeEditDocumentController.shared.documents.first(where: { doc in
guard let workspace = doc as? WorkspaceDocument, let path = self.languageServerURI else { return false }
return workspace.workspaceFileManager?.getFile(path)?.fileDocument?.isEqual(self) ?? false
}) as? WorkspaceDocument
}
}

/// `LSPService` is a service class responsible for managing the lifecycle and event handling
/// of Language Server Protocol (LSP) clients within the CodeEdit application. It handles the initialization,
/// communication, and termination of language servers, ensuring that code assistance features
@@ -184,12 +175,12 @@ final class LSPService: ObservableObject {
/// - Note: Must be invoked after the contents of the file are available.
/// - Parameter document: The code document that was opened.
func openDocument(_ document: CodeFileDocument) {
guard let workspace = document.findWorkspace(),
let workspacePath = workspace.fileURL?.absoluteURL.path(),
let lspLanguage = document.getLanguage().lspLanguage else {
return
}
Task.detached {
guard let workspace = await document.findWorkspace(),
let workspacePath = workspace.fileURL?.absoluteURL.path(),
let lspLanguage = await document.getLanguage().lspLanguage else {
return
}
let languageServer: LanguageServer
do {
if let server = await self.languageClients[ClientKey(lspLanguage, workspacePath)] {
7 changes: 3 additions & 4 deletions CodeEditTests/Features/LSP/LanguageServer+DocumentTests.swift
Original file line number Diff line number Diff line change
@@ -93,11 +93,10 @@ final class LanguageServerDocumentTests: XCTestCase {
}

// Create a CodeFileDocument to test with, attach it to the workspace and file
let contentType = try file.url.resourceValues(forKeys: [.contentTypeKey]).contentType
let codeFile = try CodeFileDocument(
for: file.url,
withContentsOf: file.url,
ofType: contentType?.identifier ?? ""
ofType: "public.swift-source"
)
file.fileDocument = codeFile

@@ -113,7 +112,7 @@ final class LanguageServerDocumentTests: XCTestCase {
eventCountExpectation.fulfill()
}

await fulfillment(of: [eventCountExpectation], timeout: 5)
await fulfillment(of: [eventCountExpectation], timeout: 2)

// This should then trigger a documentDidClose event
codeFile.close()
@@ -125,7 +124,7 @@ final class LanguageServerDocumentTests: XCTestCase {
}
eventCloseExpectation.fulfill()
}
await fulfillment(of: [eventCloseExpectation], timeout: 5.0)
await fulfillment(of: [eventCloseExpectation], timeout: 2)

XCTAssertEqual(
connection.clientRequests.map { $0.method },

0 comments on commit f7e0253

Please sign in to comment.