Skip to content

Issue Navigator #2006

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 8 commits into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0.098",
"green" : "0.161",
"red" : "0.725"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0.255",
"green" : "0.231",
"red" : "0.855"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0.114",
"green" : "0.741",
"red" : "0.965"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0.271",
"green" : "0.784",
"red" : "0.965"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -33,6 +33,7 @@ final class WorkspaceDocument: NSDocument, ObservableObject, NSToolbarDelegate {
var editorManager: EditorManager? = EditorManager()
var statusBarViewModel: StatusBarViewModel? = StatusBarViewModel()
var utilityAreaModel: UtilityAreaViewModel? = UtilityAreaViewModel()
var issueNavigatorViewModel: IssueNavigatorViewModel? = IssueNavigatorViewModel()
var searchState: SearchState?
var openQuicklyViewModel: OpenQuicklyViewModel?
var commandsPaletteState: QuickActionsViewModel?
@@ -162,6 +163,8 @@ final class WorkspaceDocument: NSDocument, ObservableObject, NSToolbarDelegate {

editorManager?.restoreFromState(self)
utilityAreaModel?.restoreFromState(self)

issueNavigatorViewModel?.initialize(projectName: displayName)
}

override func read(from url: URL, ofType typeName: String) throws {
2 changes: 1 addition & 1 deletion CodeEdit/Features/Editor/Models/EditorManager.swift
Original file line number Diff line number Diff line change
@@ -29,7 +29,7 @@ class EditorManager: ObservableObject {
/// History of last-used editors.
var activeEditorHistory: Deque<() -> Editor?> = []

/// notify listeners whenever tab selection changes on the active editor.
/// Notify listeners whenever tab selection changes on the active editor.
var tabBarTabIdSubject = PassthroughSubject<String?, Never>()
var cancellable: AnyCancellable?

6 changes: 6 additions & 0 deletions CodeEdit/Features/LSP/LanguageServer/LanguageServer.swift
Original file line number Diff line number Diff line change
@@ -21,6 +21,8 @@ class LanguageServer {
let binary: LanguageServerBinary
/// A cache to hold responses from the server, to minimize duplicate server requests
let lspCache = LSPCache()
/// The workspace document that this server is associated with
let workspace: WorkspaceDocument

/// Tracks documents and their associated objects.
/// Use this property when adding new objects that need to track file data, or have a state associated with the
@@ -41,12 +43,14 @@ class LanguageServer {
init(
languageId: LanguageIdentifier,
binary: LanguageServerBinary,
workspace: WorkspaceDocument,
lspInstance: InitializingServer,
serverCapabilities: ServerCapabilities,
rootPath: URL
) {
self.languageId = languageId
self.binary = binary
self.workspace = workspace
self.lspInstance = lspInstance
self.serverCapabilities = serverCapabilities
self.rootPath = rootPath
@@ -71,6 +75,7 @@ class LanguageServer {
static func createServer(
for languageId: LanguageIdentifier,
with binary: LanguageServerBinary,
workspace: WorkspaceDocument,
workspacePath: String
) async throws -> LanguageServer {
let executionParams = Process.ExecutionParameters(
@@ -87,6 +92,7 @@ class LanguageServer {
return LanguageServer(
languageId: languageId,
binary: binary,
workspace: workspace,
lspInstance: server,
serverCapabilities: capabilities,
rootPath: URL(filePath: workspacePath)
45 changes: 30 additions & 15 deletions CodeEdit/Features/LSP/Service/LSPService+Events.swift
Original file line number Diff line number Diff line change
@@ -18,7 +18,7 @@ extension LSPService {
// Create a new Task to listen to the events
let task = Task.detached { [weak self] in
for await event in languageClient.lspInstance.eventSequence {
await self?.handleEvent(event, for: key)
await self?.handleEvent(event, for: languageClient)
}
}
eventListeningTasks[key] = task
@@ -31,20 +31,28 @@ extension LSPService {
}
}

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

private func handleRequest(_ request: ServerRequest) {
private func handleRequest(
_ request: ServerRequest,
_ languageClient: LanguageServer
) {
// TODO: Handle Requests
// switch request {
// case let .workspaceConfiguration(params, _):
@@ -73,15 +81,20 @@ extension LSPService {
// }
}

private func handleNotification(_ notification: ServerNotification) {
private func handleNotification(
_ notification: ServerNotification,
_ languageClient: LanguageServer
) {
// TODO: Handle Notifications
// switch notification {
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 .textDocumentPublishDiagnostics(params):
print("textDocumentPublishDiagnostics: \(params.diagnostics)")
languageClient.workspace.issueNavigatorViewModel?
.updateDiagnostics(params: params)
// case let .telemetryEvent(params):
// print("telemetryEvent: \(params)")
// case let .protocolCancelRequest(params):
@@ -90,6 +103,8 @@ extension LSPService {
// print("protocolProgress: \(params)")
// case let .protocolLogTrace(params):
// print("protocolLogTrace: \(params)")
// }
default:
return
}
}
}
8 changes: 7 additions & 1 deletion CodeEdit/Features/LSP/Service/LSPService.swift
Original file line number Diff line number Diff line change
@@ -173,6 +173,7 @@ final class LSPService: ObservableObject {
/// - Returns: The new language server.
func startServer(
for languageId: LanguageIdentifier,
workspace: WorkspaceDocument,
workspacePath: String
) async throws -> LanguageServer {
guard let serverBinary = languageConfigs[languageId] else {
@@ -184,6 +185,7 @@ final class LSPService: ObservableObject {
let server = try await LanguageServer.createServer(
for: languageId,
with: serverBinary,
workspace: workspace,
workspacePath: workspacePath
)
languageClients[ClientKey(languageId, workspacePath)] = server
@@ -208,7 +210,11 @@ final class LSPService: ObservableObject {
if let server = self.languageClients[ClientKey(lspLanguage, workspacePath)] {
languageServer = server
} else {
languageServer = try await self.startServer(for: lspLanguage, workspacePath: workspacePath)
languageServer = try await self.startServer(
for: lspLanguage,
workspace: workspace,
workspacePath: workspacePath
)
}
} catch {
// swiftlint:disable:next line_length
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// IssueNavigatorView.swift
// CodeEdit
//
// Created by Abe Malla on 3/14/25.
//

import SwiftUI

/// # Issue Navigator - Sidebar
///
/// A list that functions as an issue navigator, showing collapsible issues
/// within a project.
///
struct IssueNavigatorView: View {
var body: some View {
IssueNavigatorOutlineView()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//
// IssueNavigatorMenu.swift
// CodeEdit
//
// Created by Abe Malla on 4/3/25.
//

import SwiftUI

final class IssueNavigatorMenu: NSMenu {
var item: (any IssueNode)?

/// The workspace, for opening the item
var workspace: WorkspaceDocument?

/// The `IssueNavigatorViewController` is being called from.
/// By sending it, we can access it's variables and functions.
var sender: IssueNavigatorViewController

init(_ sender: IssueNavigatorViewController) {
self.sender = sender
super.init(title: "Options")
}

@available(*, unavailable)
required init(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

/// Creates a `NSMenuItem` depending on the given arguments
/// - Parameters:
/// - title: The title of the menu item
/// - action: A `Selector` or `nil` of the action to perform.
/// - key: A `keyEquivalent` of the menu item. Defaults to an empty `String`
/// - Returns: A `NSMenuItem` which has the target `self`
private func menuItem(_ title: String, action: Selector?, key: String = "") -> NSMenuItem {
let mItem = NSMenuItem(title: title, action: action, keyEquivalent: key)
mItem.target = self
return mItem
}

/// Configures the menu based on the current selection in the outline view.
/// - Menu items get added depending on the amount of selected items.
private func setupMenu() {
guard item != nil else { return }

let copy = menuItem("Copy", action: #selector(copyIssue))
let showInFinder = menuItem("Show in Finder", action: #selector(showInFinder))
let revealInProjectNavigator = menuItem(
"Reveal in Project Navigator",
action: #selector(revealInProjectNavigator)
)
let openInTab = menuItem("Open in Tab", action: #selector(openInTab))
let openWithExternalEditor = menuItem("Open with External Editor", action: #selector(openWithExternalEditor))

items = [
copy,
.separator(),
showInFinder,
revealInProjectNavigator,
.separator(),
openInTab,
openWithExternalEditor,
]
}

/// Updates the menu for the selected item and hides it if no item is provided.
override func update() {
removeAllItems()
setupMenu()
}
}
Loading