Skip to content

Commit 3bb827b

Browse files
authored
Merge pull request #18 from github/thyeggman/logs-unicode-chars
Render ANSI color formatting
2 parents bd2234f + 8ab0bb5 commit 3bb827b

6 files changed

Lines changed: 518 additions & 98 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,4 +412,4 @@
412412
"vscode-languageclient": "^8.0.2",
413413
"yaml": "^1.7.2"
414414
}
415-
}
415+
}

src/logs/fileProvider.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,14 @@ export class WorkflowStepLogProvider implements vscode.TextDocumentContentProvid
2929
const logInfo = parseLog(log as string);
3030
cacheLogInfo(uri, logInfo);
3131

32-
return logInfo.updatedLog;
32+
return logInfo.updatedLogLines.join("\n");
3333
} catch (e) {
3434
const respErr = e as OctokitResponse<unknown, number>;
3535
if (respErr.status === 410) {
3636
cacheLogInfo(uri, {
37-
colorFormats: [],
3837
sections: [],
39-
updatedLog: ""
38+
updatedLogLines: [],
39+
styleFormats: []
4040
});
4141

4242
return "Could not open logs, they are expired.";

src/logs/formatProvider.ts

Lines changed: 59 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,13 @@
11
import * as vscode from "vscode";
22
import {LogInfo} from "./model";
3+
import {Parser, VSCodeDefaultColors} from "./parser";
34

45
const timestampRE = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{7}Z/;
56

67
const timestampDecorationType = vscode.window.createTextEditorDecorationType({
78
color: "#99999959"
89
});
910

10-
const background = {
11-
"40": "#0c0c0c",
12-
"41": "#e74856",
13-
"42": "#16c60c",
14-
"43": "#f9f1a5",
15-
"44": "#0037da",
16-
"45": "#881798",
17-
"46": "#3a96dd",
18-
"47": "#cccccc",
19-
"100": "#767676"
20-
} as {[key: string]: string};
21-
22-
const foreground = {
23-
"30": "#0c0c0c",
24-
"31": "#e74856",
25-
"32": "#16c60c",
26-
"33": "#f9f1a5",
27-
"34": "#0037da",
28-
"35": "#881798",
29-
"36": "#3a96dd",
30-
"37": "#cccccc",
31-
"90": "#767676"
32-
} as {[key: string]: string};
33-
3411
export function updateDecorations(activeEditor: vscode.TextEditor, logInfo: LogInfo) {
3512
if (!activeEditor) {
3613
return;
@@ -50,30 +27,70 @@ export function updateDecorations(activeEditor: vscode.TextEditor, logInfo: LogI
5027
}))
5128
);
5229

53-
// Custom colors
54-
const ctypes: {
30+
// Custom decorations
31+
const decoratorTypes: {
5532
[key: string]: {type: vscode.TextEditorDecorationType; ranges: vscode.Range[]};
5633
} = {};
5734

58-
for (const colorFormat of logInfo.colorFormats) {
59-
const range = new vscode.Range(colorFormat.line, colorFormat.start, colorFormat.line, colorFormat.end);
35+
for (let lineNo = 0; lineNo < logInfo.updatedLogLines.length; lineNo++) {
36+
// .filter() preserves the order of the array
37+
const lineStyles = logInfo.styleFormats.filter(style => style.line == lineNo);
38+
let pos = 0;
39+
for (let styleNo = 0; styleNo < lineStyles.length; styleNo++) {
40+
const styleInfo = lineStyles[styleNo];
41+
const endPos = pos + styleInfo.content.length;
42+
const range = new vscode.Range(lineNo, pos, lineNo, endPos);
43+
pos = endPos;
44+
45+
if (styleInfo.style) {
46+
const key = Parser.styleKey(styleInfo.style);
47+
let fgHex = "";
48+
let bgHex = "";
49+
50+
// Convert to hex colors if RGB-formatted, or use lookup for predefined colors
51+
if (styleInfo.style.isFgRGB) {
52+
const rgbValues = styleInfo.style.fg.split(",");
53+
fgHex = rgbToHex(rgbValues);
54+
} else {
55+
fgHex = VSCodeDefaultColors[styleInfo.style.fg] ?? "";
56+
}
57+
if (styleInfo.style.isBgRGB) {
58+
const rgbValues = styleInfo.style.bg.split(",");
59+
bgHex = rgbToHex(rgbValues);
60+
} else {
61+
bgHex = VSCodeDefaultColors[styleInfo.style.bg] ?? "";
62+
}
6063

61-
// TODO default to real colors
62-
const key = `${colorFormat.color.foreground || ""}-${colorFormat.color.background || ""}`;
63-
if (!ctypes[key]) {
64-
ctypes[key] = {
65-
type: vscode.window.createTextEditorDecorationType({
66-
color: colorFormat.color.foreground && foreground[colorFormat.color.foreground],
67-
backgroundColor: colorFormat.color.background && background[colorFormat.color.background]
68-
}),
69-
ranges: [range]
70-
};
71-
} else {
72-
ctypes[key].ranges.push(range);
64+
if (!decoratorTypes[key]) {
65+
decoratorTypes[key] = {
66+
type: vscode.window.createTextEditorDecorationType({
67+
color: fgHex,
68+
backgroundColor: bgHex,
69+
fontWeight: styleInfo.style.bold ? "bold" : "normal",
70+
fontStyle: styleInfo.style.italic ? "italic" : "normal",
71+
textDecoration: styleInfo.style.underline ? "underline" : ""
72+
}),
73+
ranges: [range]
74+
};
75+
} else {
76+
decoratorTypes[key].ranges.push(range);
77+
}
78+
}
7379
}
7480
}
7581

76-
for (const ctype of Object.values(ctypes)) {
77-
activeEditor.setDecorations(ctype.type, ctype.ranges);
82+
for (const decoratorType of Object.values(decoratorTypes)) {
83+
activeEditor.setDecorations(decoratorType.type, decoratorType.ranges);
84+
}
85+
}
86+
87+
function rgbToHex(rgbValues: string[]) {
88+
let hex = "";
89+
if (rgbValues.length == 3) {
90+
hex = "#";
91+
for (let i = 0; i < 3; i++) {
92+
hex = hex.concat(parseInt(rgbValues[i]).toString(16).padStart(2, "0"));
93+
}
7894
}
95+
return hex;
7996
}

src/logs/model.ts

Lines changed: 24 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
const ansiColorRE = /\u001b\[((?:\d+;?)+)m(.*)\u001b\[0m/gm;
1+
const ansiColorRegex = /\u001b\[(\d+;?)+m/gm;
22
const groupMarker = "##[group]";
3-
const commandRE = /##\[[a-z]+\]/gm;
3+
4+
import {Parser, IStyle} from "./parser";
45

56
export enum Type {
67
Setup,
@@ -14,23 +15,16 @@ export interface LogSection {
1415
name?: string;
1516
}
1617

17-
export interface LogColorInfo {
18+
export interface LogStyleInfo {
1819
line: number;
19-
start: number;
20-
end: number;
21-
22-
color: CustomColor;
23-
}
24-
25-
export interface CustomColor {
26-
foreground?: string;
27-
background?: string;
20+
content: string;
21+
style?: IStyle;
2822
}
2923

3024
export interface LogInfo {
31-
updatedLog: string;
25+
updatedLogLines: string[];
3226
sections: LogSection[];
33-
colorFormats: LogColorInfo[];
27+
styleFormats: LogStyleInfo[];
3428
}
3529

3630
export function parseLog(log: string): LogInfo {
@@ -44,10 +38,12 @@ export function parseLog(log: string): LogInfo {
4438
// Assume there is always the setup section
4539
const sections: LogSection[] = [firstSection];
4640

47-
const colorInfo: LogColorInfo[] = [];
48-
4941
let currentRange: LogSection | null = null;
42+
43+
const parser = new Parser();
44+
const styleInfo: LogStyleInfo[] = [];
5045
const lines = log.split(/\n|\r/).filter(l => !!l);
46+
5147
let lineIdx = 0;
5248

5349
for (const line of lines) {
@@ -65,7 +61,7 @@ export function parseLog(log: string): LogInfo {
6561
sections.push(currentRange);
6662
}
6763

68-
const name = line.substr(groupMarkerStart + groupMarker.length);
64+
const name = line.substring(groupMarkerStart + groupMarker.length);
6965

7066
currentRange = {
7167
name,
@@ -75,26 +71,18 @@ export function parseLog(log: string): LogInfo {
7571
};
7672
}
7773

78-
// Remove commands
79-
lines[lineIdx] = line.replace(commandRE, "");
80-
81-
// Check for custom colors
82-
let match: RegExpExecArray | null;
83-
if ((match = ansiColorRE.exec(line))) {
84-
const colorConfig = match[1];
85-
const text = match[2];
86-
87-
colorInfo.push({
74+
const stateFragments = parser.getStates(line);
75+
for (const state of stateFragments) {
76+
styleInfo.push({
8877
line: lineIdx,
89-
color: parseCustomColor(colorConfig),
90-
start: match.index,
91-
end: match.index + text.length
78+
content: state.output,
79+
style: state.style
9280
});
93-
94-
// Remove from output
95-
lines[lineIdx] = line.replace(ansiColorRE, text);
9681
}
9782

83+
// Remove all other commands and codes from the output, we don't support those
84+
lines[lineIdx] = line.replace(ansiColorRegex, "");
85+
9886
++lineIdx;
9987
}
10088

@@ -104,23 +92,8 @@ export function parseLog(log: string): LogInfo {
10492
}
10593

10694
return {
107-
updatedLog: lines.join("\n"),
108-
sections,
109-
colorFormats: colorInfo
95+
updatedLogLines: lines,
96+
sections: sections,
97+
styleFormats: styleInfo
11098
};
11199
}
112-
113-
function parseCustomColor(str: string): CustomColor {
114-
const ret: CustomColor = {};
115-
116-
const segments = str.split(";");
117-
if (segments.length > 0) {
118-
ret.foreground = segments[0];
119-
}
120-
121-
if (segments.length > 1) {
122-
ret.background = segments[1];
123-
}
124-
125-
return ret;
126-
}

0 commit comments

Comments
 (0)