Skip to content

Commit 90ae449

Browse files
committed
Implement automatic terminal detection in DiagnosticsFormatter
1 parent 3ca3c0b commit 90ae449

File tree

3 files changed

+101
-5
lines changed

3 files changed

+101
-5
lines changed

Sources/SwiftDiagnostics/DiagnosticsFormatter.swift

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,21 @@ import SwiftSyntax
1414

1515
public struct DiagnosticsFormatter {
1616

17+
public static let defaultContextSize: Int = 2
18+
19+
/// Determines how colors are used in printed diagnostics.
20+
public enum ColorMode {
21+
/// Colors are used if stderr is detected to be connected to a TTY; otherwise, colors will not
22+
/// be used (for example, if stderr is redirected to a file). See: `TerminalHelper.isConnectedToTerminal`
23+
case auto
24+
25+
/// Colors will not be used.
26+
case off
27+
28+
/// Colors will always be used.
29+
case on
30+
}
31+
1732
/// A wrapper struct for a source line and its diagnostics
1833
private struct AnnotatedSourceLine {
1934
var diagnostics: [Diagnostic]
@@ -26,21 +41,46 @@ public struct DiagnosticsFormatter {
2641
/// Whether to colorize formatted diagnostics.
2742
public let colorize: Bool
2843

29-
public init(contextSize: Int = 2, colorize: Bool = false) {
44+
public init(contextSize: Int = defaultContextSize, colorize: Bool = false) {
3045
self.contextSize = contextSize
3146
self.colorize = colorize
3247
}
3348

49+
public init(contextSize: Int = defaultContextSize, colorMode: ColorMode) {
50+
var colorize: Bool
51+
52+
switch colorMode {
53+
case .auto:
54+
colorize = TerminalHelper.isConnectedToTerminal
55+
case .off:
56+
colorize = false
57+
case .on:
58+
colorize = true
59+
}
60+
61+
self.init(contextSize: contextSize, colorize: colorize)
62+
}
63+
3464
public static func annotatedSource<SyntaxType: SyntaxProtocol>(
3565
tree: SyntaxType,
3666
diags: [Diagnostic],
37-
contextSize: Int = 2,
67+
contextSize: Int = defaultContextSize,
3868
colorize: Bool = false
3969
) -> String {
4070
let formatter = DiagnosticsFormatter(contextSize: contextSize, colorize: colorize)
4171
return formatter.annotatedSource(tree: tree, diags: diags)
4272
}
4373

74+
public static func annotatedSource<SyntaxType: SyntaxProtocol>(
75+
tree: SyntaxType,
76+
diags: [Diagnostic],
77+
contextSize: Int = defaultContextSize,
78+
colorMode: ColorMode
79+
) -> String {
80+
let formatter = DiagnosticsFormatter(contextSize: contextSize, colorMode: colorMode)
81+
return formatter.annotatedSource(tree: tree, diags: diags)
82+
}
83+
4484
/// Print given diagnostics for a given syntax tree on the command line
4585
public func annotatedSource<SyntaxType: SyntaxProtocol>(tree: SyntaxType, diags: [Diagnostic]) -> String {
4686
let slc = SourceLocationConverter(file: "", tree: tree)
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#if canImport(Glibc)
14+
import Glibc
15+
#elseif os(Windows)
16+
import CRT
17+
#else
18+
import Darwin.C
19+
#endif
20+
21+
#if os(Android)
22+
typealias FILEPointer = OpaquePointer
23+
#else
24+
typealias FILEPointer = UnsafeMutablePointer<FILE>
25+
#endif
26+
27+
enum TerminalHelper {
28+
static var isConnectedToTerminal: Bool {
29+
return isTTY(stderr)
30+
}
31+
32+
/// Checks if passed file pointer is a tty.
33+
static func isTTY(_ filePointer: FILEPointer) -> Bool {
34+
return terminalType(filePointer) == .tty
35+
}
36+
37+
/// The type of terminal.
38+
enum TerminalType {
39+
/// The terminal is a TTY.
40+
case tty
41+
42+
/// The terminal is a file stream.
43+
case file
44+
}
45+
46+
/// Computes the terminal type of the stream.
47+
static func terminalType(_ filePointer: FILEPointer) -> TerminalType {
48+
let isTTY = isatty(fileno(filePointer)) != 0
49+
return isTTY ? .tty : .file
50+
}
51+
}

Sources/swift-parser-cli/swift-parser-cli.swift

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -198,17 +198,22 @@ class PrintDiags: ParsableCommand {
198198
@Flag(name: .long, help: "Perform sequence folding with the standard operators")
199199
var foldSequences: Bool = false
200200

201-
@Flag(name: .long, help: "Colorize output with ANSI color codes")
201+
@Flag(name: .long, help: "Force output coloring with ANSI color codes")
202202
var colorize: Bool = false
203203

204204
func run() throws {
205205
let source = try getContentsOfSourceFile(at: sourceFile)
206206

207207
source.withUnsafeBufferPointer { sourceBuffer in
208208
let tree = Parser.parse(source: sourceBuffer)
209-
210209
var diags = ParseDiagnosticsGenerator.diagnostics(for: tree)
211-
print(DiagnosticsFormatter.annotatedSource(tree: tree, diags: diags, colorize: colorize))
210+
let annotatedSource = DiagnosticsFormatter.annotatedSource(
211+
tree: tree,
212+
diags: diags,
213+
colorMode: colorize ? .on : .auto
214+
)
215+
216+
print(annotatedSource)
212217

213218
if foldSequences {
214219
diags += foldAllSequences(tree).1

0 commit comments

Comments
 (0)