@trendr/core 0.4.5 → 0.4.7
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/buffer.js +15 -0
- package/src/diff-view.js +32 -4
- package/src/renderer.js +5 -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/buffer.js
CHANGED
|
@@ -129,6 +129,21 @@ export function dimBuffer(buf) {
|
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
export function dimRect(buf, x, y, w, h) {
|
|
133
|
+
const x1 = Math.max(x, 0)
|
|
134
|
+
const y1 = Math.max(y, 0)
|
|
135
|
+
const x2 = Math.min(x + w, buf.width)
|
|
136
|
+
const y2 = Math.min(y + h, buf.height)
|
|
137
|
+
for (let row = y1; row < y2; row++) {
|
|
138
|
+
const base = row * buf.width
|
|
139
|
+
for (let col = x1; col < x2; col++) {
|
|
140
|
+
const cell = buf.cells[base + col]
|
|
141
|
+
if (cell.attrs & 2) continue
|
|
142
|
+
buf.cells[base + col] = { ch: cell.ch, fg: cell.fg, bg: cell.bg, attrs: cell.attrs | 2 }
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
132
147
|
export function copyBuffer(src, dst) {
|
|
133
148
|
const len = Math.min(src.cells.length, dst.cells.length)
|
|
134
149
|
for (let i = 0; i < len; i++) dst.cells[i] = src.cells[i]
|
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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createBuffer, clearBuffer, fillRect, writeText, dimBuffer, blitRect } from './buffer.js'
|
|
1
|
+
import { createBuffer, clearBuffer, fillRect, writeText, dimBuffer, dimRect, blitRect } from './buffer.js'
|
|
2
2
|
import { diff } from './diff.js'
|
|
3
3
|
import { bufferToLines } from './serialize.js'
|
|
4
4
|
import { computeLayout, resolveBorderEdges, intrinsicHeight } from './layout.js'
|
|
@@ -409,6 +409,10 @@ function paintTree(node, buf, clip, offset, prevBuf) {
|
|
|
409
409
|
paintTree(child, buf, childClip, childOffset, childPrevBuf)
|
|
410
410
|
}
|
|
411
411
|
}
|
|
412
|
+
|
|
413
|
+
// style.dim on a box dims its whole painted region after children render:
|
|
414
|
+
// the scoped sibling of the overlay backdrop's full-buffer dim
|
|
415
|
+
if (style.dim) dimRect(buf, clipped.x, clipped.y, clipped.width, clipped.height)
|
|
412
416
|
}
|
|
413
417
|
|
|
414
418
|
function extractText(node, parentCtx) {
|
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
|
|