Skip to content

Commit 20f768b

Browse files
Styling updates
1 parent f491963 commit 20f768b

File tree

5 files changed

+169
-73
lines changed

5 files changed

+169
-73
lines changed

CodeEdit/Features/NavigatorArea/IssueNavigator/OutlineView/IssueNavigatorViewController+NSOutlineViewDataSource.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@ import AppKit
1010
extension IssueNavigatorViewController: NSOutlineViewDataSource {
1111
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
1212
if item == nil {
13-
// Always show the project node
14-
return 1
13+
// If there are no issues, don't show the project node
14+
if let rootNode = workspace?.issueNavigatorViewModel?.filteredRootNode {
15+
return rootNode.files.isEmpty ? 0 : 1
16+
}
17+
return 0
1518
}
1619
if let node = item as? ProjectIssueNode {
1720
return node.files.count

CodeEdit/Features/NavigatorArea/IssueNavigator/OutlineView/IssueNavigatorViewController+NSOutlineViewDelegate.swift

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,21 +26,44 @@ extension IssueNavigatorViewController: NSOutlineViewDelegate {
2626
let frameRect = NSRect(x: 0, y: 0, width: tableColumn.width, height: rowHeight)
2727

2828
if let node = item as? (any IssueNode) {
29-
let cell = IssueTableViewCell(frame: frameRect, node: node)
30-
return cell
29+
return IssueTableViewCell(frame: frameRect, node: node)
3130
}
32-
33-
let cell = TextTableViewCell(frame: frameRect, startingText: "Unknown item")
34-
return cell
31+
return TextTableViewCell(frame: frameRect, startingText: "Unknown item")
3532
}
3633

3734
func outlineView(_ outlineView: NSOutlineView, heightOfRowByItem item: Any) -> CGFloat {
38-
if item is DiagnosticIssueNode {
39-
// TODO: DIAGNOSTIC CELLS SHOULD MIGHT SPAN MULTIPLE ROWS, SO SHOW MULTIPLE LINES
40-
let lines = Double(Settings.shared.preferences.general.issueNavigatorDetail.rawValue)
41-
return rowHeight * lines
35+
if let diagnosticNode = item as? DiagnosticIssueNode {
36+
let columnWidth = outlineView.tableColumns.first?.width ?? outlineView.frame.width
37+
let indentationLevel = outlineView.level(forItem: item)
38+
let indentationSpace = CGFloat(indentationLevel) * outlineView.indentationPerLevel
39+
let availableWidth = columnWidth - indentationSpace - 24
40+
41+
// Create a temporary text field for measurement
42+
let tempView = NSTextField(wrappingLabelWithString: diagnosticNode.name)
43+
tempView.allowsDefaultTighteningForTruncation = false
44+
tempView.cell?.truncatesLastVisibleLine = true
45+
tempView.cell?.wraps = true
46+
tempView.maximumNumberOfLines = Settings.shared.preferences.general.issueNavigatorDetail.rawValue
47+
tempView.preferredMaxLayoutWidth = availableWidth
48+
49+
let height = tempView.sizeThatFits(
50+
NSSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)
51+
).height
52+
return max(height + 8, rowHeight)
4253
}
43-
return rowHeight // This can be changed to 20 to match Xcode's row height.
54+
return rowHeight
55+
}
56+
57+
func outlineViewColumnDidResize(_ notification: Notification) {
58+
// Disable animations temporarily
59+
NSAnimationContext.beginGrouping()
60+
NSAnimationContext.current.duration = 0
61+
62+
let indexes = IndexSet(integersIn: 0..<outlineView.numberOfRows)
63+
outlineView.noteHeightOfRows(withIndexesChanged: indexes)
64+
65+
NSAnimationContext.endGrouping()
66+
outlineView.layoutSubtreeIfNeeded()
4467
}
4568

4669
/// Adds a tooltip to the issue row.

CodeEdit/Features/NavigatorArea/OutlineView/IssueTableViewCell.swift

Lines changed: 129 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -7,77 +7,156 @@
77

88
import AppKit
99

10-
class IssueTableViewCell: StandardTableViewCell {
10+
final class IssueTableViewCell: NSTableCellView {
11+
private var label: NSTextField!
12+
private var icon: NSImageView!
13+
private var secondaryLabel: NSTextField?
14+
private var node: (any IssueNode)
1115

12-
private var nodeIconView: NSImageView?
16+
let iconWidth: CGFloat = 22
1317

14-
var issueNode: (any IssueNode)?
18+
init(frame: CGRect, node: (any IssueNode)) {
19+
self.node = node
20+
super.init(frame: frame)
1521

16-
/// Initializes the `IssueTableViewCell` with the issue node item
17-
/// - Parameters:
18-
/// - frameRect: The frame of the cell.
19-
/// - node: The issue node the cell represents.
20-
/// - isEditable: Set to true if the user should be able to edit the name (rarely used for issues).
21-
init(frame frameRect: NSRect, node: (any IssueNode)?, isEditable: Bool = false) {
22-
super.init(frame: frameRect, isEditable: isEditable)
23-
self.issueNode = node
22+
// Set up icon
23+
icon = NSImageView(frame: .zero)
24+
icon.translatesAutoresizingMaskIntoConstraints = false
25+
icon.symbolConfiguration = .init(pointSize: 13, weight: .regular, scale: .medium)
2426

25-
configureForNode(node)
27+
// Set the icon color based on the type of issue node
28+
if let projectIssueNode = node as? ProjectIssueNode {
29+
icon?.image = projectIssueNode.nsIcon
30+
icon?.contentTintColor = NSColor.folderBlue
31+
32+
let issuesCount = projectIssueNode.errorCount + projectIssueNode.warningCount
33+
let pluralizationKey = issuesCount == 1 ? "issue" : "issues"
34+
createSecondaryLabel(value: "\(issuesCount) \(pluralizationKey)")
35+
} else if let fileIssueNode = node as? FileIssueNode {
36+
icon?.image = fileIssueNode.nsIcon
37+
if Settings.shared.preferences.general.fileIconStyle == .color {
38+
icon?.contentTintColor = NSColor(fileIssueNode.iconColor)
39+
} else {
40+
icon?.contentTintColor = NSColor.coolGray
41+
}
42+
} else if let diagnosticNode = node as? DiagnosticIssueNode {
43+
icon?.image = diagnosticNode.nsIcon
44+
.withSymbolConfiguration(
45+
NSImage.SymbolConfiguration(paletteColors: [.white, diagnosticNode.severityColor])
46+
)
47+
icon?.contentTintColor = diagnosticNode.severityColor
48+
}
49+
50+
self.addSubview(icon)
51+
self.imageView = icon
52+
53+
createLabel()
54+
setConstraints()
2655
}
2756

28-
override func configLabel(label: NSTextField, isEditable: Bool) {
29-
super.configLabel(label: label, isEditable: isEditable)
57+
func createLabel() {
58+
if let diagnosticNode = node as? DiagnosticIssueNode {
59+
label = NSTextField(wrappingLabelWithString: diagnosticNode.name)
60+
} else {
61+
label = NSTextField(labelWithString: node.name)
62+
}
3063

31-
if issueNode is DiagnosticIssueNode {
32-
label.maximumNumberOfLines = 4
33-
label.lineBreakMode = .byTruncatingTail
64+
label.translatesAutoresizingMaskIntoConstraints = false
65+
label.drawsBackground = false
66+
label.isBordered = false
67+
label.isEditable = false
68+
label.isSelectable = false
69+
label.layer?.cornerRadius = 10.0
70+
label.font = .labelFont(ofSize: fontSize)
71+
72+
if node is DiagnosticIssueNode {
73+
label.maximumNumberOfLines = Settings.shared.preferences.general.issueNavigatorDetail.rawValue
74+
label.allowsDefaultTighteningForTruncation = false
75+
label.cell?.truncatesLastVisibleLine = true
3476
label.cell?.wraps = true
35-
label.cell?.isScrollable = false
36-
label.preferredMaxLayoutWidth = frame.width - iconWidth - 20
77+
label.preferredMaxLayoutWidth = frame.width
3778
} else {
3879
label.lineBreakMode = .byTruncatingTail
3980
}
40-
}
4181

42-
override func configSecondaryLabel(secondaryLabel: NSTextField) {
43-
super.configSecondaryLabel(secondaryLabel: secondaryLabel)
44-
secondaryLabel.font = .systemFont(ofSize: fontSize-2, weight: .medium)
82+
self.addSubview(label)
83+
self.textField = label
4584
}
4685

47-
func configureForNode(_ node: (any IssueNode)?) {
48-
guard let node = node else { return }
49-
50-
secondaryLabelRightAligned = true
51-
textField?.stringValue = node.name
86+
func createSecondaryLabel(value: String) {
87+
let secondaryLabel = NSTextField(frame: .zero)
88+
secondaryLabel.translatesAutoresizingMaskIntoConstraints = false
89+
secondaryLabel.drawsBackground = false
90+
secondaryLabel.isBordered = false
91+
secondaryLabel.isEditable = false
92+
secondaryLabel.isSelectable = false
93+
secondaryLabel.layer?.cornerRadius = 10.0
94+
secondaryLabel.font = .systemFont(ofSize: fontSize)
95+
secondaryLabel.alignment = .center
96+
secondaryLabel.textColor = .secondaryLabelColor
97+
secondaryLabel.stringValue = value
98+
99+
self.addSubview(secondaryLabel)
100+
self.secondaryLabel = secondaryLabel
101+
}
52102

53-
if let projectIssueNode = node as? ProjectIssueNode {
54-
imageView?.image = projectIssueNode.nsIcon
55-
imageView?.contentTintColor = NSColor.folderBlue
56-
} else if let fileIssueNode = node as? FileIssueNode {
57-
imageView?.image = fileIssueNode.nsIcon
58-
if Settings.shared.preferences.general.fileIconStyle == .color {
59-
imageView?.contentTintColor = NSColor(fileIssueNode.iconColor)
60-
} else {
61-
imageView?.contentTintColor = NSColor.coolGray
62-
}
63-
} else if let diagnosticNode = node as? DiagnosticIssueNode {
64-
imageView?.image = diagnosticNode.nsIcon
65-
.withSymbolConfiguration(
66-
NSImage.SymbolConfiguration(paletteColors: [.white, diagnosticNode.severityColor])
67-
)
68-
imageView?.contentTintColor = diagnosticNode.severityColor
103+
func setConstraints() {
104+
if node is DiagnosticIssueNode {
105+
// For diagnostic nodes, place icon at the top
106+
NSLayoutConstraint.activate([
107+
icon.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 2),
108+
icon.topAnchor.constraint(equalTo: topAnchor, constant: 4),
109+
icon.widthAnchor.constraint(equalToConstant: 16),
110+
icon.heightAnchor.constraint(equalToConstant: 16),
111+
112+
label.leadingAnchor.constraint(equalTo: icon.trailingAnchor, constant: 6),
113+
label.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -2),
114+
label.topAnchor.constraint(equalTo: topAnchor, constant: 4),
115+
label.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -4)
116+
])
117+
} else if let secondaryLabel = secondaryLabel {
118+
// Secondary label
119+
NSLayoutConstraint.activate([
120+
icon.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 2),
121+
icon.centerYAnchor.constraint(equalTo: centerYAnchor),
122+
icon.widthAnchor.constraint(equalToConstant: 16),
123+
icon.heightAnchor.constraint(equalToConstant: 16),
124+
125+
label.leadingAnchor.constraint(equalTo: icon.trailingAnchor, constant: 6),
126+
label.centerYAnchor.constraint(equalTo: centerYAnchor),
127+
128+
secondaryLabel.leadingAnchor.constraint(equalTo: label.trailingAnchor, constant: 4),
129+
secondaryLabel.centerYAnchor.constraint(equalTo: centerYAnchor),
130+
secondaryLabel.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor, constant: -8)
131+
])
132+
} else {
133+
// All other nodes
134+
NSLayoutConstraint.activate([
135+
icon.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 2),
136+
icon.centerYAnchor.constraint(equalTo: centerYAnchor),
137+
icon.widthAnchor.constraint(equalToConstant: 16),
138+
icon.heightAnchor.constraint(equalToConstant: 16),
139+
140+
label.leadingAnchor.constraint(equalTo: icon.trailingAnchor, constant: 4),
141+
label.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8),
142+
label.centerYAnchor.constraint(equalTo: centerYAnchor)
143+
])
69144
}
145+
}
70146

71-
if let projectNode = node as? ProjectIssueNode {
72-
let issuesCount = projectNode.errorCount + projectNode.warningCount
147+
override func layout() {
148+
super.layout()
73149

74-
if issuesCount > 0 {
75-
secondaryLabelRightAligned = false
76-
secondaryLabel?.stringValue = "\(issuesCount) issues"
77-
}
150+
if node is DiagnosticIssueNode {
151+
let availableWidth = frame.width
152+
label.preferredMaxLayoutWidth = availableWidth
78153
}
79154
}
80155

156+
required init?(coder: NSCoder) {
157+
fatalError("init(coder:) has not been implemented")
158+
}
159+
81160
/// Returns the font size for the current row height. Defaults to `13.0`
82161
private var fontSize: Double {
83162
switch self.frame.height {
@@ -87,12 +166,4 @@ class IssueTableViewCell: StandardTableViewCell {
87166
default: return 13
88167
}
89168
}
90-
91-
override init(frame frameRect: NSRect) {
92-
super.init(frame: frameRect)
93-
}
94-
95-
required init?(coder: NSCoder) {
96-
fatalError("init?(coder: NSCoder) isn't implemented on `IssueTableViewCell`.")
97-
}
98169
}

CodeEdit/Features/NavigatorArea/OutlineView/StandardTableViewCell.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ class StandardTableViewCell: NSTableCellView {
4949
// Create the icon
5050
let icon = createIcon()
5151
configIcon(icon: icon)
52-
addSubview(icon)
5352
imageView = icon
5453

5554
// add constraints

CodeEdit/Features/NavigatorArea/ViewModels/IssueNavigatorViewModel.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ class ProjectIssueNode: IssueNode, ObservableObject {
175175
@Published var isExpanded: Bool
176176

177177
var nsIcon: NSImage {
178-
return NSImage(systemSymbolName: "folder.fill.badge.gearshape", accessibilityDescription: "Root folder")!
178+
return NSImage(systemSymbolName: "folder.fill", accessibilityDescription: "Root folder")!
179179
}
180180

181181
var isExpandable: Bool {
@@ -282,7 +282,7 @@ class DiagnosticIssueNode: IssueNode, ObservableObject {
282282
let fileUri: DocumentUri
283283

284284
var name: String {
285-
diagnostic.message
285+
diagnostic.message.trimmingCharacters(in: .newlines)
286286
}
287287

288288
var isExpandable: Bool {

0 commit comments

Comments
 (0)