@trendr/core 0.4.4 → 0.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/ansi.js +49 -24
- package/src/diff-view.js +32 -4
- package/src/renderer.js +2 -1
- package/src/wrap.js +9 -0
package/package.json
CHANGED
package/src/ansi.js
CHANGED
|
@@ -97,8 +97,21 @@ function rgbToHex(r, g, b) {
|
|
|
97
97
|
return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
// a params string reduces to a state-independent delta: fg/bg values when
|
|
101
|
+
// touched (undefined = untouched), plus and/or masks over attrs. styled text
|
|
102
|
+
// repeats the same handful of sequences thousands of times per frame, so
|
|
103
|
+
// each is parsed once and its delta replayed from cache
|
|
104
|
+
const sgrDeltaCache = new Map()
|
|
105
|
+
const SGR_CACHE_MAX = 4000
|
|
106
|
+
|
|
107
|
+
const setBit = (delta, bit) => { delta.or |= bit }
|
|
108
|
+
const clearBits = (delta, bits) => { delta.and &= ~bits; delta.or &= ~bits }
|
|
109
|
+
|
|
110
|
+
function sgrDelta(params) {
|
|
111
|
+
const cached = sgrDeltaCache.get(params)
|
|
112
|
+
if (cached) return cached
|
|
113
|
+
|
|
114
|
+
const delta = { fg: undefined, bg: undefined, and: ~0, or: 0 }
|
|
102
115
|
const codes = params.split(';').map(Number)
|
|
103
116
|
let i = 0
|
|
104
117
|
|
|
@@ -106,46 +119,58 @@ export function parseSgr(params, state) {
|
|
|
106
119
|
const c = codes[i]
|
|
107
120
|
|
|
108
121
|
if (c === 0) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
else if (c ===
|
|
114
|
-
else if (c ===
|
|
115
|
-
else if (c ===
|
|
116
|
-
else if (c ===
|
|
117
|
-
else if (c ===
|
|
118
|
-
else if (c ===
|
|
119
|
-
else if (c ===
|
|
120
|
-
else if (c ===
|
|
121
|
-
else if (c ===
|
|
122
|
-
else if (c ===
|
|
123
|
-
else if (c ===
|
|
124
|
-
else if (c ===
|
|
122
|
+
delta.fg = null
|
|
123
|
+
delta.bg = null
|
|
124
|
+
delta.and = 0
|
|
125
|
+
delta.or = 0
|
|
126
|
+
} else if (c === 1) setBit(delta, BOLD)
|
|
127
|
+
else if (c === 2) setBit(delta, DIM)
|
|
128
|
+
else if (c === 3) setBit(delta, ITALIC)
|
|
129
|
+
else if (c === 4) setBit(delta, UNDERLINE)
|
|
130
|
+
else if (c === 7) setBit(delta, INVERSE)
|
|
131
|
+
else if (c === 9) setBit(delta, STRIKETHROUGH)
|
|
132
|
+
else if (c === 22) clearBits(delta, BOLD | DIM)
|
|
133
|
+
else if (c === 23) clearBits(delta, ITALIC)
|
|
134
|
+
else if (c === 24) clearBits(delta, UNDERLINE)
|
|
135
|
+
else if (c === 27) clearBits(delta, INVERSE)
|
|
136
|
+
else if (c === 29) clearBits(delta, STRIKETHROUGH)
|
|
137
|
+
else if (c === 39) delta.fg = null
|
|
138
|
+
else if (c === 49) delta.bg = null
|
|
125
139
|
else if (c === 38 && codes[i + 1] === 5) {
|
|
126
|
-
|
|
140
|
+
delta.fg = indexToColor(codes[i + 2])
|
|
127
141
|
i += 2
|
|
128
142
|
} else if (c === 48 && codes[i + 1] === 5) {
|
|
129
|
-
|
|
143
|
+
delta.bg = indexToColor(codes[i + 2])
|
|
130
144
|
i += 2
|
|
131
145
|
} else if (c === 38 && codes[i + 1] === 2) {
|
|
132
|
-
|
|
146
|
+
delta.fg = rgbToHex(codes[i + 2], codes[i + 3], codes[i + 4])
|
|
133
147
|
i += 4
|
|
134
148
|
} else if (c === 48 && codes[i + 1] === 2) {
|
|
135
|
-
|
|
149
|
+
delta.bg = rgbToHex(codes[i + 2], codes[i + 3], codes[i + 4])
|
|
136
150
|
i += 4
|
|
137
151
|
} else {
|
|
138
152
|
const fgIdx = basicFgToIndex(c)
|
|
139
|
-
if (fgIdx != null)
|
|
153
|
+
if (fgIdx != null) delta.fg = indexToColor(fgIdx)
|
|
140
154
|
else {
|
|
141
155
|
const bgIdx = basicBgToIndex(c)
|
|
142
|
-
if (bgIdx != null)
|
|
156
|
+
if (bgIdx != null) delta.bg = indexToColor(bgIdx)
|
|
143
157
|
}
|
|
144
158
|
}
|
|
145
159
|
|
|
146
160
|
i++
|
|
147
161
|
}
|
|
148
162
|
|
|
163
|
+
if (sgrDeltaCache.size >= SGR_CACHE_MAX) sgrDeltaCache.clear()
|
|
164
|
+
sgrDeltaCache.set(params, delta)
|
|
165
|
+
return delta
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function parseSgr(params, state) {
|
|
169
|
+
if (!state) state = { fg: null, bg: null, attrs: 0 }
|
|
170
|
+
const delta = sgrDelta(params)
|
|
171
|
+
if (delta.fg !== undefined) state.fg = delta.fg
|
|
172
|
+
if (delta.bg !== undefined) state.bg = delta.bg
|
|
173
|
+
state.attrs = (state.attrs & delta.and) | delta.or
|
|
149
174
|
return state
|
|
150
175
|
}
|
|
151
176
|
|
package/src/diff-view.js
CHANGED
|
@@ -38,6 +38,32 @@ function intraSegments(ansiLine, plainLen, ranges) {
|
|
|
38
38
|
return segs
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
// components re-execute every frame but diff inputs are immutable strings;
|
|
42
|
+
// recomputing a word diff per card per frame dominates long transcripts.
|
|
43
|
+
// whole mode (before/after strings) is memoized; patch/hunks callers pass
|
|
44
|
+
// fresh objects per render, so they fall through to a direct compute
|
|
45
|
+
const diffMemo = new Map()
|
|
46
|
+
const DIFF_MEMO_MAX = 300
|
|
47
|
+
const hiLineCache = new Map()
|
|
48
|
+
const HI_CACHE_MAX = 2000
|
|
49
|
+
|
|
50
|
+
function memoComputeDiff({ before, after, patch, hunks, wordDiff, context }) {
|
|
51
|
+
if (patch != null || hunks != null || before == null || after == null) {
|
|
52
|
+
return computeDiff({ before, after, patch, hunks, wordDiff, context })
|
|
53
|
+
}
|
|
54
|
+
let byAfter = diffMemo.get(before)
|
|
55
|
+
if (!byAfter) {
|
|
56
|
+
if (diffMemo.size >= DIFF_MEMO_MAX) diffMemo.clear()
|
|
57
|
+
diffMemo.set(before, (byAfter = new Map()))
|
|
58
|
+
}
|
|
59
|
+
const key = `${wordDiff}|${context}|${after.length}\x00${after.slice(0, 64)}`
|
|
60
|
+
const inner = byAfter.get(key)
|
|
61
|
+
if (inner && inner.after === after) return inner.result
|
|
62
|
+
const result = computeDiff({ before, after, wordDiff, context })
|
|
63
|
+
byAfter.set(key, { after, result })
|
|
64
|
+
return result
|
|
65
|
+
}
|
|
66
|
+
|
|
41
67
|
export function Diff({
|
|
42
68
|
before,
|
|
43
69
|
after,
|
|
@@ -62,14 +88,16 @@ export function Diff({
|
|
|
62
88
|
const offset = offsetProp ?? offsetInternal()
|
|
63
89
|
const setOffset = onScroll ?? setOffsetInternal
|
|
64
90
|
|
|
65
|
-
const { rows, stats } =
|
|
91
|
+
const { rows, stats } = memoComputeDiff({ before, after, patch, hunks, wordDiff, context })
|
|
66
92
|
|
|
67
93
|
const wholeMode = hunks == null && patch == null
|
|
68
|
-
const hiCache = new Map()
|
|
69
94
|
const hiLines = (text) => {
|
|
70
|
-
|
|
95
|
+
const key = `${language}\x00${text}`
|
|
96
|
+
const cached = hiLineCache.get(key)
|
|
97
|
+
if (cached) return cached
|
|
71
98
|
const lines = (highlight ? highlight(text, language) : text).split('\n')
|
|
72
|
-
|
|
99
|
+
if (hiLineCache.size >= HI_CACHE_MAX) hiLineCache.clear()
|
|
100
|
+
hiLineCache.set(key, lines)
|
|
73
101
|
return lines
|
|
74
102
|
}
|
|
75
103
|
const beforeHi = wholeMode && highlight ? hiLines(before ?? '') : null
|
package/src/renderer.js
CHANGED
|
@@ -948,6 +948,7 @@ export function mount(rootComponent, { stream, stdin, title, theme, onExit: onEx
|
|
|
948
948
|
let lastInlineBuf = null
|
|
949
949
|
|
|
950
950
|
function frame() {
|
|
951
|
+
const frameStart = performance.now()
|
|
951
952
|
const prevCtx = activeContext
|
|
952
953
|
activeContext = ctx
|
|
953
954
|
overlays = []
|
|
@@ -1076,7 +1077,7 @@ export function mount(rootComponent, { stream, stdin, title, theme, onExit: onEx
|
|
|
1076
1077
|
}
|
|
1077
1078
|
lastFrameTimestamp = now
|
|
1078
1079
|
const avgMs = frameTimeWindow.length > 0 ? frameTimeWindow.reduce((a, b) => a + b, 0) / frameTimeWindow.length : 16.67
|
|
1079
|
-
lastFrameStats = { changed, total: width * height, bytes: output ? Buffer.byteLength(output) : 0, fps: Math.round(1000 / avgMs) }
|
|
1080
|
+
lastFrameStats = { changed, total: width * height, bytes: output ? Buffer.byteLength(output) : 0, fps: Math.round(1000 / avgMs), renderMs: performance.now() - frameStart }
|
|
1080
1081
|
|
|
1081
1082
|
const tmp = prev
|
|
1082
1083
|
prev = curr
|
package/src/wrap.js
CHANGED
|
@@ -34,7 +34,14 @@ const TAB_STOP = 8
|
|
|
34
34
|
|
|
35
35
|
const tabWidth = (col) => TAB_STOP - (col % TAB_STOP)
|
|
36
36
|
|
|
37
|
+
// text nodes are measured every layout pass but their strings are immutable;
|
|
38
|
+
// transcripts re-measure thousands of identical strings per frame
|
|
39
|
+
const measureCache = new Map()
|
|
40
|
+
const MEASURE_CACHE_MAX = 20000
|
|
41
|
+
|
|
37
42
|
export function measureText(text) {
|
|
43
|
+
const hit = measureCache.get(text)
|
|
44
|
+
if (hit !== undefined) return hit
|
|
38
45
|
const clean = stripAnsi(text)
|
|
39
46
|
let width = 0
|
|
40
47
|
for (let i = 0; i < clean.length; i++) {
|
|
@@ -42,6 +49,8 @@ export function measureText(text) {
|
|
|
42
49
|
if (code > 0xffff) i++
|
|
43
50
|
width += code === 9 ? tabWidth(width) : charWidth(code)
|
|
44
51
|
}
|
|
52
|
+
if (measureCache.size >= MEASURE_CACHE_MAX) measureCache.clear()
|
|
53
|
+
measureCache.set(text, width)
|
|
45
54
|
return width
|
|
46
55
|
}
|
|
47
56
|
|