enhance(actions): replace ansi_up with first-party code (#38619)

Replaces the `ansi_up` dependency with first-party code and fixes a
number of bugs in turn.

- Faster rendering, around 7x for plain lines and 3x for colored ones.
- Render many SGR features like hyperlinks, blink, inverse, conceal,
strikethrough, overline, underline styles and underline color, including
`:` sub-parameters, which no longer swallow the codes after them.
- Drop OSC, DCS, SOS, PM and APC with their payload, ending them at BEL,
`ESC \` or the 8-bit ST. A truncated sequence is dropped instead of
corrupting a later line.
- A backspace moves the cursor back a column, so what follows overwrites
it, even across a style change.
- A style inside an OSC 8 label renders instead of leaking, and a
private CSI ending in `m` no longer resets the style.
- Log lines render as DOM nodes, never as markup, and only an `http(s)`
url becomes a link.
- Named colors render as CSS classes, only 24-bit color stays inline.
- Invisible text is now selectable, and the `z-index` workaround is
gone.

Signed-off-by: silverwind <me@silverwind.io>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
silverwind
2026-08-05 23:59:26 +02:00
committed by GitHub
parent 42e0c9eca4
commit 74804df4a5
11 changed files with 419 additions and 105 deletions
+7 -6
View File
@@ -10,6 +10,7 @@ import type {IntervalId} from '../types.ts';
import {toggleFullScreen} from '../utils.ts';
import {localUserSettings} from '../modules/user-settings.ts';
import type {ActionsArtifact, ActionsJob, ActionsRun, ActionsStatus} from '../modules/gitea-actions.ts';
import {AnsiLineRenderer} from '../render/ansi.ts';
import {
type ActionRunViewStore,
createLogLineMessage,
@@ -36,6 +37,9 @@ type JobStepState = {
manuallyCollapsed: boolean, // whether the user manually collapsed the step, used to avoid auto-expanding it again
}
// one ANSI renderer per step, so an unterminated color carries between that step's lines only
const stepAnsiRenderers: AnsiLineRenderer[] = [];
type StepContainerElement = HTMLElement & {
// To remember the last active logs container, for example: a batch of logs only starts a group but doesn't end it,
// then the following batches of logs should still use the same group (active logs container).
@@ -213,10 +217,11 @@ async function copyStepOutput(event: MouseEvent, stepIndex: number) {
const data = await fetchJobData([{step: stepIndex, cursor: null, expanded: true}]);
const stepLog = data.logs.stepsLog?.find((s) => s.step === stepIndex);
const lines: string[] = [];
const ansi = new AnsiLineRenderer();
for (const line of stepLog?.lines ?? []) {
const cmd = parseLogLineCommand(line);
if (cmd?.name === 'hidden' || cmd?.name === 'endgroup') continue;
const msg = createLogLineMessage(line, cmd).textContent ?? '';
const msg = createLogLineMessage(ansi, line, cmd).textContent ?? '';
lines.push(timeVisible.value['log-time-stamp'] ? `${formatDatetimeISO(line.timestamp)} ${msg}` : msg);
}
return lines.join('\n');
@@ -240,7 +245,7 @@ function createLogLine(stepIndex: number, startTime: number, line: LogLine, cmd:
const logTimeStamp = createElementFromAttrs('span', {class: 'log-time-stamp'},
formatDatetime(line.timestamp * 1000), // for "Show timestamps"
);
const logMsg = createLogLineMessage(line, cmd);
const logMsg = createLogLineMessage(stepAnsiRenderers[stepIndex] ??= new AnsiLineRenderer(), line, cmd);
const seconds = Math.floor(line.timestamp - startTime);
const logTimeSeconds = createElementFromAttrs('span', {class: 'log-time-seconds'},
`${seconds}s`, // for "Show seconds"
@@ -598,7 +603,6 @@ async function hashChangeListener() {
.job-step-container {
max-height: 100%;
border-radius: 0 0 var(--border-radius) var(--border-radius);
z-index: 0;
}
.job-step-container .job-step-summary {
@@ -651,9 +655,6 @@ async function hashChangeListener() {
background-color: var(--color-console-active-bg);
position: sticky;
top: 60px;
/* workaround ansi_up issue related to faintStyle generating a CSS stacking context via `opacity`
inline style which caused such elements to render above the .job-step-summary header. */
z-index: 1;
}
</style>