Skip to content
Merged
Show file tree
Hide file tree
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
66 changes: 53 additions & 13 deletions Sources/XCDocsCLI/CLIOutput.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Darwin
import ArgumentParser
import Foundation
import XCDocs
Expand All @@ -15,18 +16,57 @@ func printJSON<T: Encodable>(_ value: T, prettyPrinted: Bool = true) throws {
@available(macOS 26, *)
func printSearchResults(_ results: [SearchResult]) {
for (index, result) in results.enumerated() {
let renderedScore = result.score.isFinite ? String(format: "%.4f", result.score) : "nan"
print("\(index + 1). [\(renderedScore)] \(result.entry.id)")

let metadata = [result.entry.framework, result.entry.kind?.rawValue, result.entry.title].compactMap { $0 }
.joined(separator: " | ")
if !metadata.isEmpty { print(" \(metadata)") }

if let content = result.entry.content, !content.isEmpty {
let singleLineContent = content.replacingOccurrences(of: "\n", with: " ").trimmingCharacters(
in: .whitespacesAndNewlines
)
print(" \(singleLineContent)")
}
print(renderTextEntry(result.entry, score: result.score, index: index + 1), terminator: "")
}
}

@available(macOS 26, *)
func renderTextEntry(_ entry: DocumentationEntry, score: Double? = nil, index: Int? = nil) -> String {
let supportsStyling = terminalSupportsANSIStyling()
let prefix = index.map { "\($0). " } ?? ""
let indentation = String(repeating: " ", count: prefix.count)
let headline = prefix + (entry.title ?? entry.id)

var lines = [renderBold(headline, enabled: supportsStyling)]

if let kind = entry.kind?.rawValue {
lines.append("\(indentation)\(renderBold("Kind:", enabled: supportsStyling)) \(kind)")
}

if let score {
let renderedScore = score.isFinite ? String(format: "%.4f", score) : "nan"
lines.append("\(indentation)\(renderBold("Relevance:", enabled: supportsStyling)) \(renderedScore)")
}

lines.append("\(indentation)\(renderBold("ID:", enabled: supportsStyling)) \(entry.id)")
lines.append("")

if let content = entry.content?.trimmingCharacters(in: .whitespacesAndNewlines), !content.isEmpty {
lines.append(contentsOf: renderIndentedContentLines(content, indentation: indentation))
}

lines.append("")
lines.append("")
return lines.joined(separator: "\n")
}
Comment thread
julianschiavo marked this conversation as resolved.

private func renderIndentedContentLines(_ content: String, indentation: String) -> [String] {
content.split(separator: "\n", omittingEmptySubsequences: false).map { "\(indentation)\($0)" }
}

private func renderBold(_ text: String, enabled: Bool) -> String {
guard enabled else { return text }
return "\u{001B}[1m\(text)\u{001B}[22m"
}

private func terminalSupportsANSIStyling() -> Bool {
if environmentValue(named: "CLICOLOR_FORCE") == "1" { return true }
if environmentValue(named: "NO_COLOR") != nil { return false }
guard isatty(STDOUT_FILENO) != 0 else { return false }
return environmentValue(named: "TERM") != "dumb"
}

private func environmentValue(named name: String) -> String? {
guard let value = getenv(name) else { return nil }
return String(cString: value)
}
15 changes: 1 addition & 14 deletions Sources/XCDocsCLI/GetCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,6 @@ struct GetCommand: AsyncParsableCommand {
return
}

printEntry(result)
}
}

@available(macOS 26, *)
private func printEntry(_ result: DocumentationEntry) {
print(result.id)

let metadata = [result.framework, result.kind?.rawValue, result.title].compactMap { $0 }.joined(separator: " | ")
if !metadata.isEmpty { print(metadata) }

if let content = result.content, !content.isEmpty {
print("")
print(content)
print(renderTextEntry(result), terminator: "")
}
}
101 changes: 101 additions & 0 deletions Tests/XCDocsCLITests/SearchTextOutputTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import Darwin
import Testing
import XCDocs

@testable import XCDocsCLI

@Suite("Search Text Output")
struct SearchTextOutputTests {
Comment thread
julianschiavo marked this conversation as resolved.
@Test
func searchResultUsesExpandedMetadataLayout() throws {
guard #available(macOS 26, *) else { return }
try assertSearchResultUsesExpandedMetadataLayout()
}

@Test
func searchResultAlignsMetadataWithDoubleDigitIndex() throws {
guard #available(macOS 26, *) else { return }
try assertSearchResultAlignsMetadataWithDoubleDigitIndex()
}

@Test
func getOutputUsesNoLeadingIndentation() throws {
guard #available(macOS 26, *) else { return }
try assertGetOutputUsesNoLeadingIndentation()
}
}

@available(macOS 26, *)
private func assertSearchResultUsesExpandedMetadataLayout() throws {
let result = makeSearchResult(
score: 0.5405,
id: "/documentation/Metal/MTLClearColor/init(red:green:blue:alpha:)",
title: "init(red:green:blue:alpha:)"
)

let output = try withEnvironment(variable: "CLICOLOR_FORCE", value: "1") {
renderTextEntry(result.entry, score: result.score, index: 1)
}

let expectedOutput = [
bold("1. init(red:green:blue:alpha:)"), " \(bold("Kind:")) symbol", " \(bold("Relevance:")) 0.5405",
" \(bold("ID:")) /documentation/Metal/MTLClearColor/init(red:green:blue:alpha:)", "", " [content]", "", "",
].joined(separator: "\n")

#expect(output == expectedOutput)
}

@available(macOS 26, *)
private func assertSearchResultAlignsMetadataWithDoubleDigitIndex() throws {
let result = makeSearchResult(score: 0.5120, id: "/documentation/Testing/result-12", title: "Result 12")

let output = try withEnvironment(variable: "CLICOLOR_FORCE", value: "1") {
renderTextEntry(result.entry, score: result.score, index: 12)
}

let expectedBlock = [
bold("12. Result 12"), " \(bold("Kind:")) symbol", " \(bold("Relevance:")) 0.5120",
" \(bold("ID:")) /documentation/Testing/result-12", "", " [content]", "", "",
].joined(separator: "\n")

#expect(output.contains(expectedBlock))
}

@available(macOS 26, *)
private func assertGetOutputUsesNoLeadingIndentation() throws {
let entry = DocumentationEntry(
id: "/documentation/Metal/MTLClearColor/init(red:green:blue:alpha:)",
framework: "Metal",
kind: .symbol,
title: "init(red:green:blue:alpha:)",
content: "[content]"
)

let output = try withEnvironment(variable: "CLICOLOR_FORCE", value: "1") { renderTextEntry(entry) }

let expectedOutput = [
bold("init(red:green:blue:alpha:)"), "\(bold("Kind:")) symbol",
"\(bold("ID:")) /documentation/Metal/MTLClearColor/init(red:green:blue:alpha:)", "", "[content]", "", "",
].joined(separator: "\n")

#expect(output == expectedOutput)
}

@available(macOS 26, *)
private func makeSearchResult(score: Double, id: String, title: String) -> SearchResult {
SearchResult(
score: score,
entry: DocumentationEntry(id: id, framework: "Metal", kind: .symbol, title: title, content: "[content]")
)
}

private func bold(_ string: String) -> String { "\u{001B}[1m\(string)\u{001B}[22m" }

private func withEnvironment<T>(variable: String, value: String, _ work: () throws -> T) throws -> T {
let previousValue = getenv(variable).map { String(cString: $0) }
setenv(variable, value, 1)

defer { if let previousValue { setenv(variable, previousValue, 1) } else { unsetenv(variable) } }

return try work()
}
Loading