@l1yp/file-viewer 0.1.4 → 0.1.5
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/README.md +2 -0
- package/dist/components/AnsiText.d.ts +47 -0
- package/dist/components/AnsiText.js +41 -0
- package/dist/components/AnsiText.js.map +1 -0
- package/dist/components/CodeView.js +1 -1
- package/dist/components/CodeView.js.map +1 -1
- package/dist/components/CodeView.vue_vue_type_script_setup_true_lang.js +80 -71
- package/dist/components/CodeView.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/DocxEmbeddedFiles.js +9 -0
- package/dist/components/DocxEmbeddedFiles.js.map +1 -0
- package/dist/components/DocxEmbeddedFiles.vue.d.ts +15 -0
- package/dist/components/DocxEmbeddedFiles.vue_vue_type_script_setup_true_lang.js +69 -0
- package/dist/components/DocxEmbeddedFiles.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/DocxViewer.js +1 -1
- package/dist/components/DocxViewer.js.map +1 -1
- package/dist/components/DocxViewer.vue_vue_type_script_setup_true_lang.js +180 -50
- package/dist/components/DocxViewer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/FileViewerBody.js +1 -1
- package/dist/components/FileViewerBody.js.map +1 -1
- package/dist/components/FileViewerBody.vue.d.ts +1 -0
- package/dist/components/FileViewerBody.vue_vue_type_script_setup_true_lang.js +117 -114
- package/dist/components/FileViewerBody.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/FileViewerPanel.js +1 -1
- package/dist/components/FileViewerPanel.js.map +1 -1
- package/dist/components/FileViewerPanel.vue_vue_type_script_setup_true_lang.js +18 -18
- package/dist/components/FileViewerPanel.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/LogViewer.js +1 -1
- package/dist/components/LogViewer.js.map +1 -1
- package/dist/components/LogViewer.vue.d.ts +3 -1
- package/dist/components/LogViewer.vue_vue_type_script_setup_true_lang.js +284 -280
- package/dist/components/LogViewer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/lib/ansi.d.ts +44 -0
- package/dist/lib/ansi.js +224 -0
- package/dist/lib/ansi.js.map +1 -0
- package/dist/lib/cfb.d.ts +37 -0
- package/dist/lib/cfb.js +112 -0
- package/dist/lib/cfb.js.map +1 -0
- package/dist/lib/docxEmbeddedDom.d.ts +22 -0
- package/dist/lib/docxEmbeddedDom.js +128 -0
- package/dist/lib/docxEmbeddedDom.js.map +1 -0
- package/dist/lib/docxEmbeddings.d.ts +41 -0
- package/dist/lib/docxEmbeddings.js +226 -0
- package/dist/lib/docxEmbeddings.js.map +1 -0
- package/dist/lib/msi.js +62 -154
- package/dist/lib/msi.js.map +1 -1
- package/dist/lib/olePackage.d.ts +8 -0
- package/dist/lib/olePackage.js +37 -0
- package/dist/lib/olePackage.js.map +1 -0
- package/dist/lib/pptxFirstSlide.d.ts +11 -0
- package/dist/lib/pptxFirstSlide.js +34 -0
- package/dist/lib/pptxFirstSlide.js.map +1 -0
- package/dist/style.css +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LogViewer.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/LogViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\nimport { formatBytes } from '@/lib/format'\nimport Icon from '@/components/icons/Icon.vue'\nimport { useLogRules, type ColorRule } from '@/composables/useLogRules'\n\n/**\n * Side-viewer for large text / log files. Fetches the bytes, splits into lines, and renders them with a\n * fixed-height VIRTUAL list — only the visible window (plus a little overscan) is ever in the DOM, so a\n * multi-hundred-MB log scrolls smoothly instead of locking the page (the plain source view would mount one\n * huge highlighted block). No syntax highlight (logs don't need it, and tokenizing 500k lines is infeasible).\n *\n * Routed here for any `.log`, and for any large text file (see FileChip's size gate) so a 50MB `.txt`/`.csv`\n * doesn't freeze the normal viewer. Tools: a sticky line-number gutter, substring search (highlight, match\n * count, prev/next, optional filter-to-matches), go-to-line, plus three log-specific aids:\n *\n * 1. Call-stack folding — error logs from common languages (Java/Kotlin/Scala, JS/Node, Python, C#/.NET,\n * Ruby) are one header line (\"Exception in thread…\", \"Traceback…\", \"Caused by:\") followed by a run of\n * indented frame lines (\" at …\", ' File \"…\"', …). We detect those runs, hang a chevron off the header,\n * and collapse the frames behind a \"N 帧\" badge (collapsed by DEFAULT). A live search force-expands any\n * stack with a hit; filter mode pulls a matched frame's WHOLE stack in (see {@link filtered}).\n * 2. Context expansion (filter mode) — between filtered chunks sits a \"⋯ N 行\" expander bar; click it to\n * progressively reveal hidden lines around a match (a chunk from the top, from the bottom, or all), the\n * way JetBrains' diff reveals collapsed unchanged regions. Revealed lines are tracked in {@link revealed}.\n * 3. Color rules — user-defined substring/regex rules paint matching lines' background/text (see\n * {@link useLogRules}); applied to visible rows only via {@link styleFor}.\n */\nconst props = defineProps<{ url?: string; name: string; size?: number }>()\n\ntype Status = 'loading' | 'gate' | 'ready' | 'error'\nconst status = ref<Status>('loading')\nconst errorMsg = ref('')\nconst lines = ref<string[]>([])\n\n// Reading the whole file into memory is unavoidable here (the endpoint has no range support); gate the\n// truly huge ones behind a confirm.\nconst SIZE_LIMIT = 64 * 1024 * 1024\nconst LINE_H = 19 // px — MUST match the .log__row CSS height\nconst OVERSCAN = 12\nconst STEP = 10 // lines revealed per directional context-expand click\n\nconst scrollEl = ref<HTMLElement | null>(null)\nconst scrollTop = ref(0)\nconst viewportH = ref(400)\nlet ro: ResizeObserver | null = null\n\n// ---- stack-trace detection ----------------------------------------------------------------------\n// A stack group spans original lines [header .. frameEnd]; `header` is always `frameStart - 1` (the error /\n// \"Caused by:\" / \"Traceback:\" line just above the run), and the collapsible frames are [frameStart .. frameEnd].\ntype Group = { header: number; frameStart: number; frameEnd: number }\n\n// Only fold a run with at least this many frame lines (folding a lone frame saves nothing).\nconst MIN_FRAMES = 2\n\n// Indented stack-frame lines across common languages, matched on the raw line:\n// RE_AT Java/Kotlin/Scala/JS/Node/C#/.NET/PHP \" at pkg.Class.method(File:12)\" / \"at fn (file.js:1:2)\"\n// (requires a '.' or '(' after the symbol so a prose line like \" at 3pm the job ran\" won't match)\n// RE_FILE Python ' File \"/x.py\", line 10, in fn'\n// RE_FROM Ruby \" from a.rb:5:in `m'\"\n// RE_MORE Java \" ... 12 more\"\n// RE_NET C#/.NET async boundary \" --- End of stack trace from previous location ---\"\nconst RE_AT = /^[ \\t]+at\\s+\\S+[.(]/\nconst RE_FILE = /^[ \\t]+File\\s[\"']/\nconst RE_FROM = /^[ \\t]+from\\s+\\S+:\\d+/\nconst RE_MORE = /^[ \\t]*\\.\\.\\.\\s+\\d+\\s+more\\b/\nconst RE_NET = /^[ \\t]+--- End of /\nfunction isFrame(line: string): boolean {\n return (\n RE_AT.test(line) ||\n RE_FILE.test(line) ||\n RE_FROM.test(line) ||\n RE_MORE.test(line) ||\n RE_NET.test(line)\n )\n}\n\n// Group consecutive frame runs. Python frames come in two lines (the `File \"…\"` line plus the indented source\n// line under it) — absorb that trailing source line into the run so it folds with its frame.\nconst groups = computed<Group[]>(() => {\n const src = lines.value\n const N = src.length\n const out: Group[] = []\n let i = 0\n while (i < N) {\n if (!isFrame(src[i])) {\n i++\n continue\n }\n const runStart = i\n let prevFile = RE_FILE.test(src[i])\n i++\n while (i < N) {\n const ln = src[i]\n if (isFrame(ln)) {\n prevFile = RE_FILE.test(ln)\n i++\n } else if (prevFile && /^[ \\t]+\\S/.test(ln)) {\n prevFile = false // Python source line under a `File \"…\"` frame\n i++\n } else break\n }\n const runEnd = i - 1\n // Prefer the (non-blank) plain line just above the run as the header; otherwise treat the run's first line\n // as the header and fold the rest.\n const above = runStart - 1\n const useAbove = above >= 0 && !isFrame(src[above]) && src[above].trim() !== ''\n const frameStart = useAbove ? runStart : runStart + 1\n if (runEnd - frameStart + 1 >= MIN_FRAMES) {\n out.push({ header: frameStart - 1, frameStart, frameEnd: runEnd })\n }\n }\n return out\n})\n\nconst hasGroups = computed(() => groups.value.length > 0)\n\n// origLine → groupId, for header lines only.\nconst headerMap = computed(() => {\n const m = new Map<number, number>()\n groups.value.forEach((g, i) => m.set(g.header, i))\n return m\n})\n\n// The group whose span [header .. frameEnd] contains `orig` (groups are position-sorted & disjoint), or -1.\nfunction groupOf(orig: number): number {\n const gs = groups.value\n let lo = 0\n let hi = gs.length - 1\n while (lo <= hi) {\n const mid = (lo + hi) >> 1\n const g = gs[mid]\n if (orig < g.header) hi = mid - 1\n else if (orig > g.frameEnd) lo = mid + 1\n else return mid\n }\n return -1\n}\n\n// User-expanded group ids (default = absent = collapsed). Reset on each load.\nconst expanded = ref<Set<number>>(new Set())\nconst allExpanded = computed(() => hasGroups.value && expanded.value.size === groups.value.length)\n\nfunction toggleGroup(id: number): void {\n const s = new Set(expanded.value)\n if (s.has(id)) s.delete(id)\n else s.add(id)\n expanded.value = s\n}\nfunction toggleAll(): void {\n expanded.value = allExpanded.value ? new Set() : new Set(groups.value.map((_, i) => i))\n}\n\n// Is this group folded right now? Collapsed unless the user expanded it — OR a live query has a hit inside it\n// (force-expand so the match can't hide behind the fold).\nfunction isCollapsed(id: number): boolean {\n if (expanded.value.has(id)) return false\n if (debounced.value && includedGroups.value.has(id)) return false\n return true\n}\n\n// ---- color rules --------------------------------------------------------------------------------\n\nconst { rules, add: addRule, remove: removeRule, move: moveRule } = useLogRules()\nconst rulesOpen = ref(false)\n\n// Enabled, non-empty rules pre-compiled to a matcher (+ colors). Invalid regex / color-less rules are dropped.\nconst compiledRules = computed(() => {\n const out: { test: (s: string) => boolean; bg: string; fg: string }[] = []\n for (const r of rules.value) {\n if (!r.enabled || !r.pattern) continue\n if (!(r.bgOn || r.fgOn)) continue\n let test: ((s: string) => boolean) | null\n if (r.regex) {\n try {\n const re = new RegExp(r.pattern, r.caseSensitive ? '' : 'i')\n test = (s) => re.test(s)\n } catch {\n test = null // invalid regex → skip until the user fixes it\n }\n } else if (r.caseSensitive) {\n const n = r.pattern\n test = (s) => s.includes(n)\n } else {\n const n = r.pattern.toLowerCase()\n test = (s) => s.toLowerCase().includes(n)\n }\n if (test) out.push({ test, bg: r.bgOn ? r.bg : '', fg: r.fgOn ? r.fg : '' })\n }\n return out\n})\n\n// First matching rule's colors for a line, or null. Called per VISIBLE row only.\nfunction styleFor(text: string): { bg: string; fg: string } | null {\n for (const c of compiledRules.value) {\n if (c.test(text)) return { bg: c.bg, fg: c.fg }\n }\n return null\n}\n\n// A regex rule with a syntax error — used to flag the input red in the editor.\nfunction regexBad(r: ColorRule): boolean {\n if (!r.regex || !r.pattern) return false\n try {\n new RegExp(r.pattern)\n return false\n } catch {\n return true\n }\n}\n\n// ---- search -------------------------------------------------------------------------------------\n\nconst query = ref('')\nconst debounced = ref('')\nlet debounceTimer: ReturnType<typeof setTimeout> | undefined\nwatch(query, (q) => {\n clearTimeout(debounceTimer)\n debounceTimer = setTimeout(() => {\n debounced.value = q\n currentMatch.value = 0\n revealed.value = new Set() // context is relative to the current filter → drop it when the query changes\n }, 160)\n})\n\n// Clear the search box (× button / Esc) — apply immediately instead of waiting on the debounce.\nfunction clearSearch(): void {\n query.value = ''\n clearTimeout(debounceTimer)\n debounced.value = ''\n currentMatch.value = 0\n revealed.value = new Set()\n}\n\nconst filterMode = ref(false)\nconst errorOnly = ref(false)\nconst currentMatch = ref(0)\n\n// Original line indices (0-based) that contain the query.\nconst matchIdx = computed(() => {\n const q = debounced.value.toLowerCase()\n if (!q) return [] as number[]\n const out: number[] = []\n const src = lines.value\n for (let i = 0; i < src.length; i++) {\n if (src[i].toLowerCase().includes(q)) out.push(i)\n }\n return out\n})\n\nfunction hitsQuery(text: string, q: string): boolean {\n return q ? text.toLowerCase().includes(q.toLowerCase()) : false\n}\n\n// Groups with at least one match (header or any frame). Drives both force-expand and filter-keeps-stack.\nconst includedGroups = computed(() => {\n const s = new Set<number>()\n if (!debounced.value) return s\n for (const m of matchIdx.value) {\n const g = groupOf(m)\n if (g >= 0) s.add(g)\n }\n return s\n})\n\n// The currently highlighted match's original line index (for the active-row tint), or -1.\nconst activeOrig = computed(() => {\n const m = matchIdx.value\n return m.length ? m[Math.min(currentMatch.value, m.length - 1)] : -1\n})\n\n// ---- \"only errors\" filter -----------------------------------------------------------------------\n// Error / exception lines for the 只看异常 quick filter. Case-sensitive on purpose: uppercase level tokens,\n// PascalCase *Exception / *Error class names, and a few explicit markers. Lowercase \"error\"/\"warn\" are NOT\n// matched (too noisy in ordinary messages) — a stack's header line is picked up via headerMap instead.\nconst ERROR_RE =\n /\\b(?:ERROR|FATAL|SEVERE|CRITICAL|PANIC|FAILED|FAILURE)\\b|\\b\\w*(?:Exception|Error)\\b|\\bTraceback\\b|\\bCaused by\\b|^\\s*panic:/\n\n// Error-line indices (only computed while the toggle is on). Stack headers always count as errors.\nconst errorIdx = computed<number[]>(() => {\n if (!errorOnly.value) return []\n const src = lines.value\n const hdr = headerMap.value\n const out: number[] = []\n for (let i = 0; i < src.length; i++) {\n if (hdr.has(i) || ERROR_RE.test(src[i])) out.push(i)\n }\n return out\n})\nconst errorSet = computed(() => new Set(errorIdx.value))\n\n// The search \"过滤\" toggle only bites when there's a query.\nconst searchFilterActive = computed(() => filterMode.value && debounced.value !== '')\n\n// The anchor lines the filtered view is built around (null ⇒ no filter → show the whole log via `runs`).\n// Search + errors compose as an intersection (\"errors that also match the query\"); either alone is its own set.\nconst anchors = computed<number[] | null>(() => {\n const s = searchFilterActive.value\n const e = errorOnly.value\n if (!s && !e) return null\n if (s && e) {\n const es = errorSet.value\n return matchIdx.value.filter((i) => es.has(i))\n }\n return e ? errorIdx.value : matchIdx.value\n})\n\n// Keep whole stacks around anchors only when the search filter contributes (its contract: 过滤保留整条堆栈).\n// The pure 只看异常 view stays compact — one line per error, the stack peekable via the context expander.\nconst keepStacks = computed(() => searchFilterActive.value)\n\n// ---- context expansion (filter mode) ------------------------------------------------------------\n// Original line indices the user has manually revealed as context around matches. Reset on load / query change.\nconst revealed = ref<Set<number>>(new Set())\n// Switching filters changes what the gaps mean → drop revealed context.\nwatch([errorOnly, filterMode], () => {\n revealed.value = new Set()\n})\n\n// Reveal a chunk of a gap between shown lines (top, bottom are the shown lines bracketing it; -1 / N at file\n// edges): 'down' from the top match, 'up' toward the bottom match, or 'all'.\nfunction reveal(top: number, bottom: number, dir: 'up' | 'down' | 'all'): void {\n const lo = top + 1\n const hi = bottom - 1\n if (hi < lo) return\n let a = lo\n let b = hi\n if (dir === 'down') b = Math.min(hi, lo + STEP - 1)\n else if (dir === 'up') a = Math.max(lo, hi - STEP + 1)\n const s = new Set(revealed.value)\n for (let i = a; i <= b; i++) s.add(i)\n revealed.value = s\n}\n\n// ---- display layout -----------------------------------------------------------------------------\n// Two modes drive the virtual list:\n// - Non-filter: every line shows, minus the frames of collapsed groups. Expressed as `runs` — the visible\n// original-index segments between the hidden frame ranges — so display↔original stays a cheap binary search\n// (no per-line array; big files stay light).\n// - Filter: only matches show, but every included group contributes its WHOLE span so stacks stay intact,\n// plus any manually-revealed context lines; the remaining gaps become expander rows. Materialized as an\n// item list (bounded by what's shown, normally small).\n\n// Non-filter: visible segments + total row count.\nconst runs = computed(() => {\n const gs = groups.value\n const N = lines.value.length\n const out: { o0: number; o1: number; d0: number }[] = []\n let disp = 0\n let cursor = 0\n for (let id = 0; id < gs.length; id++) {\n if (!isCollapsed(id)) continue\n const g = gs[id]\n if (g.frameStart > cursor) {\n // visible run up to and including the header (header === frameStart - 1)\n out.push({ o0: cursor, o1: g.frameStart - 1, d0: disp })\n disp += g.frameStart - cursor\n }\n cursor = g.frameEnd + 1 // skip the folded frames\n }\n if (cursor < N) {\n out.push({ o0: cursor, o1: N - 1, d0: disp })\n disp += N - cursor\n }\n return { runs: out, total: disp }\n})\n\n// Filter-mode items: `line` rows plus `gap` expander rows. `key` is a parallel, strictly-increasing lookup\n// array (lines → their orig, gaps → top + 0.5) so origToDisplay can binary-search a match's row.\ntype FItem = { t: 'line'; orig: number } | { t: 'gap'; top: number; bottom: number; size: number }\nconst filtered = computed<{ items: FItem[]; key: number[] } | null>(() => {\n const anc = anchors.value\n if (anc === null) return null\n const N = lines.value.length\n const gs = groups.value\n // Stacks kept whole around anchors — only when the search filter contributes (see keepStacks).\n const ranges: [number, number][] = []\n if (keepStacks.value) {\n const inc = new Set<number>()\n for (const a of anc) {\n const g = groupOf(a)\n if (g >= 0) inc.add(g)\n }\n for (const id of inc) ranges.push([gs[id].header, gs[id].frameEnd])\n ranges.sort((a, b) => a[0] - b[0])\n }\n const covered = (o: number): boolean => {\n let lo = 0\n let hi = ranges.length - 1\n while (lo <= hi) {\n const mid = (lo + hi) >> 1\n if (o < ranges[mid][0]) hi = mid - 1\n else if (o > ranges[mid][1]) lo = mid + 1\n else return true\n }\n return false\n }\n // Standalone shown lines = anchors + revealed context, minus whatever a kept-whole stack already covers.\n const extra: number[] = []\n for (const a of anc) if (!covered(a)) extra.push(a)\n for (const r of revealed.value) if (!covered(r)) extra.push(r)\n extra.sort((a, b) => a - b)\n // Merge (sorted) range indices with (sorted) standalone lines → sorted, de-duped base set.\n const base: number[] = []\n let ri = 0\n let si = 0\n let cur = ranges.length ? ranges[0][0] : Infinity\n let last = -1\n while (ri < ranges.length || si < extra.length) {\n const rn = ri < ranges.length ? cur : Infinity\n const sn = si < extra.length ? extra[si] : Infinity\n let v: number\n if (rn <= sn) {\n v = rn\n if (cur >= ranges[ri][1]) {\n ri++\n cur = ri < ranges.length ? ranges[ri][0] : Infinity\n } else cur++\n } else {\n v = sn\n si++\n }\n if (v !== last) {\n base.push(v)\n last = v\n }\n }\n // Weave in gap expanders between non-adjacent shown lines (and at the file's head/tail).\n const items: FItem[] = []\n const key: number[] = []\n let prev = -1\n for (const o of base) {\n if (o - prev > 1) {\n items.push({ t: 'gap', top: prev, bottom: o, size: o - prev - 1 })\n key.push(prev + 0.5)\n }\n items.push({ t: 'line', orig: o })\n key.push(o)\n prev = o\n }\n if (prev < N - 1) {\n items.push({ t: 'gap', top: prev, bottom: N, size: N - 1 - prev })\n key.push(prev + 0.5)\n }\n return { items, key }\n})\n\nconst inFilter = computed(() => filtered.value !== null)\nconst total = computed(() => (filtered.value ? filtered.value.items.length : runs.value.total))\n\ntype RowKind = 'plain' | 'header' | 'frame'\ntype RowInfo = {\n n: number\n text: string\n kind: RowKind\n collapsed: boolean\n count: number\n gid: number\n}\n\nfunction rowInfo(orig: number): RowInfo {\n const text = lines.value[orig] ?? ''\n const gid = headerMap.value.get(orig)\n if (gid !== undefined) {\n const g = groups.value[gid]\n // Filter mode always shows groups expanded (frames must be visible to keep the stack), so no fold chrome.\n return {\n n: orig + 1,\n text,\n kind: 'header',\n collapsed: filtered.value ? false : isCollapsed(gid),\n count: g.frameEnd - g.frameStart + 1,\n gid,\n }\n }\n const fg = groupOf(orig)\n const kind: RowKind = fg >= 0 && orig >= groups.value[fg].frameStart ? 'frame' : 'plain'\n return { n: orig + 1, text, kind, collapsed: false, count: 0, gid: -1 }\n}\n\n// Non-filter: display row index → original line index (binary search over the visible runs).\nfunction origInRuns(rs: { o0: number; o1: number; d0: number }[], d: number): number {\n let lo = 0\n let hi = rs.length - 1\n let pick = 0\n while (lo <= hi) {\n const mid = (lo + hi) >> 1\n if (d < rs[mid].d0) hi = mid - 1\n else {\n pick = mid\n lo = mid + 1\n }\n }\n const r = rs[pick]\n return r ? r.o0 + (d - r.d0) : d\n}\n\nconst startIdx = computed(() => Math.max(0, Math.floor(scrollTop.value / LINE_H) - OVERSCAN))\nconst endIdx = computed(() =>\n Math.min(total.value, Math.ceil((scrollTop.value + viewportH.value) / LINE_H) + OVERSCAN),\n)\n\n// One flat shape for every visible row (content OR gap) so the template needs no union narrowing. Content\n// rows default the gap fields; gap rows default the content fields.\ntype VisRow = {\n i: number\n isGap: boolean\n n: number\n text: string\n kind: RowKind\n collapsed: boolean\n count: number\n gid: number\n style: { bg: string; fg: string } | null\n context: boolean\n top: number\n bottom: number\n size: number\n step: number\n}\nfunction contentRow(i: number, orig: number): VisRow {\n const info = rowInfo(orig)\n return {\n i,\n isGap: false,\n ...info,\n style: styleFor(info.text),\n // \"Context\" dimming is a filter-mode cue for manually-revealed lines; never dim in the full (non-filter)\n // view, where leftover `revealed` entries are irrelevant.\n context:\n filtered.value !== null && revealed.value.has(orig) && !hitsQuery(info.text, debounced.value),\n top: 0,\n bottom: 0,\n size: 0,\n step: 0,\n }\n}\nconst visible = computed<VisRow[]>(() => {\n const rows: VisRow[] = []\n const s = startIdx.value\n const e = endIdx.value\n const f = filtered.value\n if (f) {\n for (let i = s; i < e; i++) {\n const it = f.items[i]\n if (!it) continue\n if (it.t === 'gap') {\n rows.push({\n i,\n isGap: true,\n n: 0,\n text: '',\n kind: 'plain',\n collapsed: false,\n count: 0,\n gid: -1,\n style: null,\n context: false,\n top: it.top,\n bottom: it.bottom,\n size: it.size,\n step: Math.min(STEP, it.size),\n })\n } else {\n rows.push(contentRow(i, it.orig))\n }\n }\n } else {\n const rs = runs.value.runs\n for (let i = s; i < e; i++) rows.push(contentRow(i, origInRuns(rs, i)))\n }\n return rows\n})\n\nfunction escapeHtml(s: string): string {\n return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')\n}\n\n// Escape the line and wrap query occurrences in <mark> (matching on raw text, escaping each piece → safe).\nfunction renderLine(text: string): string {\n const q = debounced.value\n if (!q) return escapeHtml(text)\n const lower = text.toLowerCase()\n const ql = q.toLowerCase()\n let out = ''\n let i = 0\n let idx = lower.indexOf(ql, i)\n while (idx !== -1) {\n out += escapeHtml(text.slice(i, idx))\n out += '<mark class=\"log__hit\">' + escapeHtml(text.slice(idx, idx + q.length)) + '</mark>'\n i = idx + q.length\n idx = lower.indexOf(ql, i)\n }\n out += escapeHtml(text.slice(i))\n return out\n}\n\n// ---- navigation ---------------------------------------------------------------------------------\n\nfunction scrollToDisplay(i: number): void {\n const el = scrollEl.value\n if (el) el.scrollTop = Math.max(0, i * LINE_H - viewportH.value / 2 + LINE_H)\n}\n\n// Original line index → current display row index (or -1 if not shown). Folded frames redirect to their header.\nfunction origToDisplay(orig: number): number {\n const f = filtered.value\n if (f) {\n const k = f.key\n let lo = 0\n let hi = k.length - 1\n while (lo <= hi) {\n const mid = (lo + hi) >> 1\n if (k[mid] === orig) return mid\n if (k[mid] < orig) lo = mid + 1\n else hi = mid - 1\n }\n return -1\n }\n const g = groupOf(orig)\n if (g >= 0 && orig >= groups.value[g].frameStart && isCollapsed(g)) orig = groups.value[g].header\n const rs = runs.value.runs\n let lo = 0\n let hi = rs.length - 1\n while (lo <= hi) {\n const mid = (lo + hi) >> 1\n if (orig < rs[mid].o0) hi = mid - 1\n else if (orig > rs[mid].o1) lo = mid + 1\n else return rs[mid].d0 + (orig - rs[mid].o0)\n }\n return -1\n}\n\nfunction gotoMatch(delta: number): void {\n const m = matchIdx.value\n if (!m.length) return\n currentMatch.value = (currentMatch.value + delta + m.length) % m.length\n const d = origToDisplay(m[currentMatch.value])\n if (d >= 0) scrollToDisplay(d)\n}\n\nconst gotoLineInput = ref('')\nfunction gotoLine(): void {\n const n = parseInt(gotoLineInput.value, 10)\n if (!Number.isFinite(n)) return\n const orig = Math.max(0, Math.min(lines.value.length - 1, n - 1))\n const d = origToDisplay(orig)\n if (d >= 0) scrollToDisplay(d)\n}\n\nfunction onScroll(): void {\n if (scrollEl.value) scrollTop.value = scrollEl.value.scrollTop\n}\n\n// ---- load ---------------------------------------------------------------------------------------\n\nasync function doLoad(): Promise<void> {\n if (!props.url) return\n status.value = 'loading'\n errorMsg.value = ''\n try {\n const res = await fetch(props.url)\n if (!res.ok) throw new Error(`HTTP ${res.status}`)\n const text = await res.text()\n lines.value = text.split('\\n')\n // Drop a single trailing empty line (file ended with \\n) so there's no phantom last row.\n if (lines.value.length > 1 && lines.value[lines.value.length - 1] === '') lines.value.pop()\n expanded.value = new Set() // stacks fold by default on each fresh file\n revealed.value = new Set()\n scrollTop.value = 0\n if (scrollEl.value) scrollEl.value.scrollTop = 0\n status.value = 'ready'\n } catch (e) {\n errorMsg.value = e instanceof Error ? e.message : String(e)\n status.value = 'error'\n }\n}\n\nfunction load(): void {\n if (props.size && props.size > SIZE_LIMIT) {\n status.value = 'gate'\n return\n }\n void doLoad()\n}\n\n// scrollEl lives inside the `status === 'ready'` branch (a v-else), so it does NOT exist at mount time —\n// it appears only after the file finishes loading. Watch the ref and attach the ResizeObserver when the\n// element shows up (and re-attach if the pane is torn down + rebuilt on reload), so viewportH reflects the\n// REAL pane height. Without this it stayed at the 400px default and only ~half a screen of rows rendered,\n// leaving the rest blank until you scrolled. flush:'post' so clientHeight is read after the DOM is painted.\nwatch(\n scrollEl,\n (el) => {\n ro?.disconnect()\n if (el && typeof ResizeObserver !== 'undefined') {\n ro = new ResizeObserver(() => {\n if (scrollEl.value) viewportH.value = scrollEl.value.clientHeight\n })\n ro.observe(el)\n viewportH.value = el.clientHeight\n }\n },\n { flush: 'post' },\n)\nwatch(() => props.url, load, { immediate: true })\nonBeforeUnmount(() => {\n clearTimeout(debounceTimer)\n ro?.disconnect()\n})\n</script>\n\n<template>\n <div class=\"log\">\n <p v-if=\"status === 'loading'\" class=\"log__msg\">正在加载…</p>\n\n <div v-else-if=\"status === 'gate'\" class=\"log__msg log__gate\">\n <p>该文件较大({{ formatBytes(props.size) }}),将在浏览器内加载。是否继续?</p>\n <button type=\"button\" class=\"log__btn\" @click=\"doLoad\">加载并预览</button>\n </div>\n\n <div v-else-if=\"status === 'error'\" class=\"log__msg log__msg--error\">\n <p>加载失败:{{ errorMsg }}</p>\n <button type=\"button\" class=\"log__btn\" @click=\"doLoad\">重试</button>\n </div>\n\n <template v-else>\n <!-- toolbar: search + match nav + filter + fold-all + color rules + go-to-line -->\n <div class=\"log__bar\">\n <div class=\"log__searchwrap\">\n <input\n v-model=\"query\"\n class=\"log__search\"\n type=\"text\"\n placeholder=\"搜索…\"\n aria-label=\"搜索\"\n @keydown.esc=\"clearSearch\"\n />\n <button\n v-if=\"query\"\n type=\"button\"\n class=\"log__clear\"\n title=\"清空搜索\"\n aria-label=\"清空搜索\"\n @click=\"clearSearch\"\n >\n <Icon name=\"close\" :size=\"12\" />\n </button>\n </div>\n <span v-if=\"debounced\" class=\"log__matches\">\n {{ matchIdx.length ? currentMatch + 1 : 0 }}/{{ matchIdx.length }}\n </span>\n <button\n type=\"button\"\n class=\"log__nav\"\n title=\"上一处\"\n :disabled=\"!matchIdx.length\"\n @click=\"gotoMatch(-1)\"\n >\n ↑\n </button>\n <button\n type=\"button\"\n class=\"log__nav\"\n title=\"下一处\"\n :disabled=\"!matchIdx.length\"\n @click=\"gotoMatch(1)\"\n >\n ↓\n </button>\n <button\n type=\"button\"\n class=\"log__nav log__nav--text\"\n :class=\"{ 'log__nav--on': filterMode }\"\n :disabled=\"!debounced\"\n title=\"只看匹配行(命中的堆栈整条保留,行间可展开上下文)\"\n @click=\"filterMode = !filterMode\"\n >\n 过滤\n </button>\n <button\n type=\"button\"\n class=\"log__nav log__nav--text\"\n :class=\"{ 'log__nav--on': errorOnly }\"\n title=\"只显示异常/错误行(异常头、ERROR/FATAL、*Exception/*Error 等;可与搜索过滤叠加)\"\n @click=\"errorOnly = !errorOnly\"\n >\n 只看异常\n </button>\n <button\n v-if=\"hasGroups\"\n type=\"button\"\n class=\"log__nav log__nav--text\"\n :class=\"{ 'log__nav--on': allExpanded }\"\n :title=\"`共 ${groups.length} 处调用堆栈,点击${allExpanded ? '折叠' : '展开'}全部`\"\n @click=\"toggleAll\"\n >\n {{ allExpanded ? '折叠堆栈' : '展开堆栈' }}\n </button>\n <button\n type=\"button\"\n class=\"log__nav log__nav--text\"\n :class=\"{ 'log__nav--on': rulesOpen || compiledRules.length > 0 }\"\n title=\"按规则给行上色\"\n @click=\"rulesOpen = !rulesOpen\"\n >\n 上色\n </button>\n <span class=\"log__spacer\" />\n <input\n v-model=\"gotoLineInput\"\n class=\"log__goto\"\n type=\"text\"\n inputmode=\"numeric\"\n placeholder=\"行号\"\n aria-label=\"跳转到行\"\n @keydown.enter=\"gotoLine\"\n />\n <span class=\"log__total\">\n {{ lines.length }} 行<template v-if=\"hasGroups\"> · {{ groups.length }} 堆栈</template>\n </span>\n\n <!-- color-rules editor (dropdown). Backdrop closes on outside click. -->\n <div v-if=\"rulesOpen\" class=\"log__overlay\" @click=\"rulesOpen = false\" />\n <div v-if=\"rulesOpen\" class=\"log__rules\" @click.stop>\n <div class=\"log__rules-head\">\n <span class=\"log__rules-title\">行上色规则</span>\n <span class=\"log__rules-hint\">从上到下,首个匹配生效</span>\n </div>\n <p v-if=\"!rules.length\" class=\"log__rules-empty\">\n 还没有规则。添加一条,用关键字或正则给匹配行上色(如 <code>ERROR</code> 标红)。\n </p>\n <div v-for=\"(r, idx) in rules\" :key=\"r.id\" class=\"log__rule\">\n <input v-model=\"r.enabled\" type=\"checkbox\" title=\"启用该规则\" />\n <input\n v-model=\"r.pattern\"\n class=\"log__rule-pat\"\n :class=\"{ 'log__rule-pat--bad': regexBad(r) }\"\n type=\"text\"\n placeholder=\"关键字或正则\"\n />\n <button\n type=\"button\"\n class=\"log__rule-tog\"\n :class=\"{ 'log__rule-tog--on': r.regex }\"\n title=\"按正则匹配\"\n @click=\"r.regex = !r.regex\"\n >\n .*\n </button>\n <button\n type=\"button\"\n class=\"log__rule-tog\"\n :class=\"{ 'log__rule-tog--on': r.caseSensitive }\"\n title=\"区分大小写\"\n @click=\"r.caseSensitive = !r.caseSensitive\"\n >\n Aa\n </button>\n <label class=\"log__rule-color\" title=\"背景色\">\n <input v-model=\"r.bgOn\" type=\"checkbox\" />\n <input v-model=\"r.bg\" type=\"color\" :disabled=\"!r.bgOn\" />\n <span>底</span>\n </label>\n <label class=\"log__rule-color\" title=\"字体色\">\n <input v-model=\"r.fgOn\" type=\"checkbox\" />\n <input v-model=\"r.fg\" type=\"color\" :disabled=\"!r.fgOn\" />\n <span>字</span>\n </label>\n <button\n type=\"button\"\n class=\"log__rule-ic\"\n title=\"上移\"\n :disabled=\"idx === 0\"\n @click=\"moveRule(r.id, -1)\"\n >\n <Icon name=\"arrowUp\" :size=\"12\" />\n </button>\n <button\n type=\"button\"\n class=\"log__rule-ic\"\n title=\"下移\"\n :disabled=\"idx === rules.length - 1\"\n @click=\"moveRule(r.id, 1)\"\n >\n <Icon name=\"arrowDown\" :size=\"12\" />\n </button>\n <button\n type=\"button\"\n class=\"log__rule-ic log__rule-del\"\n title=\"删除\"\n @click=\"removeRule(r.id)\"\n >\n <Icon name=\"trash\" :size=\"12\" />\n </button>\n </div>\n <button type=\"button\" class=\"log__rules-add\" @click=\"addRule\">\n <Icon name=\"plus\" :size=\"12\" /> 添加规则\n </button>\n </div>\n </div>\n\n <!-- virtual list: a fixed-height sizer drives the scrollbar; only the visible window is mounted -->\n <div ref=\"scrollEl\" class=\"log__scroll\" @scroll=\"onScroll\">\n <div class=\"log__sizer\" :style=\"{ height: total * LINE_H + 'px' }\">\n <div class=\"log__rows\" :style=\"{ paddingTop: startIdx * LINE_H + 'px' }\">\n <div\n v-for=\"row in visible\"\n :key=\"row.i\"\n class=\"log__row\"\n :class=\"{\n log__gaprow: row.isGap,\n 'log__row--active': !row.isGap && row.n - 1 === activeOrig,\n 'log__row--header': !row.isGap && row.kind === 'header',\n 'log__row--frame': !row.isGap && row.kind === 'frame',\n 'log__row--context': row.context,\n }\"\n :style=\"\n !row.isGap && row.style && row.style.bg ? { background: row.style.bg } : undefined\n \"\n >\n <!-- gap expander (filter mode): reveal hidden context around matches -->\n <template v-if=\"row.isGap\">\n <button\n v-if=\"row.size > STEP\"\n type=\"button\"\n class=\"log__gapbtn\"\n :title=\"`向下展开 ${row.step} 行(上方匹配之后)`\"\n @click=\"reveal(row.top, row.bottom, 'down')\"\n >\n <Icon name=\"chevronDown\" :size=\"12\" />\n </button>\n <button\n type=\"button\"\n class=\"log__gapmain\"\n :title=\"`展开全部 ${row.size} 行`\"\n @click=\"reveal(row.top, row.bottom, 'all')\"\n >\n ⋯ {{ row.size }} 行\n </button>\n <button\n v-if=\"row.size > STEP\"\n type=\"button\"\n class=\"log__gapbtn log__gapbtn--up\"\n :title=\"`向上展开 ${row.step} 行(下方匹配之前)`\"\n @click=\"reveal(row.top, row.bottom, 'up')\"\n >\n <Icon name=\"chevronDown\" :size=\"12\" />\n </button>\n </template>\n\n <!-- content row -->\n <template v-else>\n <span class=\"log__ln\">{{ row.n }}</span>\n <span v-if=\"hasGroups\" class=\"log__fold-cell\">\n <button\n v-if=\"row.kind === 'header' && !inFilter\"\n type=\"button\"\n class=\"log__fold\"\n :title=\"row.collapsed ? '展开堆栈' : '折叠堆栈'\"\n @click=\"toggleGroup(row.gid)\"\n >\n <Icon\n name=\"chevronRight\"\n :size=\"12\"\n class=\"log__chev\"\n :class=\"{ 'log__chev--open': !row.collapsed }\"\n />\n </button>\n </span>\n <span\n class=\"log__tx\"\n :style=\"row.style && row.style.fg ? { color: row.style.fg } : undefined\"\n v-html=\"renderLine(row.text)\"\n />\n <button\n v-if=\"row.kind === 'header' && row.collapsed && !inFilter\"\n type=\"button\"\n class=\"log__badge\"\n title=\"展开堆栈\"\n @click=\"toggleGroup(row.gid)\"\n >\n ⋯ {{ row.count }} 帧\n </button>\n </template>\n </div>\n </div>\n </div>\n </div>\n </template>\n </div>\n</template>\n\n<style scoped>\n.log {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n background: var(--code-bg);\n}\n\n.log__msg {\n padding: 20px 16px;\n color: var(--muted);\n font-size: 13px;\n}\n.log__msg--error {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 10px;\n color: var(--danger);\n}\n.log__gate {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 12px;\n}\n.log__btn {\n font-family: inherit;\n font-size: 12.5px;\n color: var(--text);\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 8px;\n padding: 5px 12px;\n cursor: pointer;\n}\n.log__btn:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n\n.log__bar {\n position: relative;\n display: flex;\n align-items: center;\n gap: 6px;\n flex-shrink: 0;\n padding: 8px 12px;\n border-bottom: 1px solid var(--border);\n background: var(--surface);\n}\n.log__searchwrap {\n position: relative;\n flex: 1 1 auto;\n min-width: 60px;\n display: flex;\n}\n.log__search {\n flex: 1 1 auto;\n min-width: 0;\n padding: 5px 28px 5px 10px;\n font-family: inherit;\n font-size: 12.5px;\n color: var(--text);\n background: var(--code-bg);\n border: 1px solid var(--border-2);\n border-radius: 7px;\n}\n.log__search:focus,\n.log__goto:focus {\n outline: none;\n border-color: var(--accent);\n}\n.log__clear {\n position: absolute;\n top: 50%;\n right: 5px;\n transform: translateY(-50%);\n display: flex;\n align-items: center;\n justify-content: center;\n width: 19px;\n height: 19px;\n padding: 0;\n color: var(--faint);\n background: none;\n border: none;\n border-radius: 5px;\n cursor: pointer;\n}\n.log__clear:hover {\n color: var(--text);\n background: var(--surface-2);\n}\n.log__matches {\n flex-shrink: 0;\n font-family: var(--font-mono);\n font-size: 11.5px;\n color: var(--faint);\n min-width: 38px;\n text-align: center;\n}\n.log__nav {\n flex-shrink: 0;\n min-width: 26px;\n height: 26px;\n padding: 0 6px;\n font-family: inherit;\n font-size: 12px;\n color: var(--muted);\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 6px;\n cursor: pointer;\n}\n.log__nav:hover:not(:disabled) {\n border-color: var(--accent);\n color: var(--accent);\n}\n.log__nav:disabled {\n opacity: 0.4;\n cursor: default;\n}\n.log__nav--on {\n color: var(--accent);\n border-color: var(--accent);\n background: var(--surface-2);\n}\n.log__spacer {\n flex: 1 1 auto;\n}\n.log__goto {\n flex-shrink: 0;\n width: 64px;\n padding: 5px 8px;\n font-family: var(--font-mono);\n font-size: 12px;\n color: var(--text);\n background: var(--code-bg);\n border: 1px solid var(--border-2);\n border-radius: 7px;\n}\n.log__total {\n flex-shrink: 0;\n font-size: 11.5px;\n color: var(--faint);\n white-space: nowrap;\n}\n\n/* color-rules dropdown */\n.log__overlay {\n position: fixed;\n inset: 0;\n z-index: 40;\n background: transparent;\n}\n.log__rules {\n position: absolute;\n z-index: 41;\n top: calc(100% + 4px);\n right: 12px;\n width: min(520px, calc(100vw - 24px));\n max-height: 320px;\n overflow: auto;\n padding: 10px;\n display: flex;\n flex-direction: column;\n gap: 6px;\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 10px;\n box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);\n}\n.log__rules-head {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 8px;\n}\n.log__rules-title {\n font-size: 12.5px;\n font-weight: 600;\n color: var(--text);\n}\n.log__rules-hint {\n font-size: 11px;\n color: var(--faint);\n}\n.log__rules-empty {\n font-size: 12px;\n color: var(--muted);\n line-height: 1.5;\n margin: 2px 0;\n}\n.log__rules-empty code {\n font-family: var(--font-mono);\n font-size: 11px;\n padding: 0 4px;\n background: var(--code-bg);\n border-radius: 4px;\n}\n.log__rule {\n display: flex;\n align-items: center;\n gap: 5px;\n}\n.log__rule-pat {\n flex: 1 1 auto;\n min-width: 80px;\n padding: 4px 8px;\n font-family: var(--font-mono);\n font-size: 12px;\n color: var(--text);\n background: var(--code-bg);\n border: 1px solid var(--border-2);\n border-radius: 6px;\n}\n.log__rule-pat:focus {\n outline: none;\n border-color: var(--accent);\n}\n.log__rule-pat--bad {\n border-color: var(--danger);\n color: var(--danger);\n}\n.log__rule-tog {\n flex-shrink: 0;\n width: 26px;\n height: 26px;\n padding: 0;\n font-family: var(--font-mono);\n font-size: 11px;\n color: var(--muted);\n background: var(--code-bg);\n border: 1px solid var(--border-2);\n border-radius: 6px;\n cursor: pointer;\n}\n.log__rule-tog--on {\n color: var(--accent);\n border-color: var(--accent);\n background: var(--surface-2);\n}\n.log__rule-color {\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n gap: 3px;\n font-size: 11px;\n color: var(--faint);\n cursor: pointer;\n}\n.log__rule-color input[type='color'] {\n width: 22px;\n height: 22px;\n padding: 0;\n border: 1px solid var(--border-2);\n border-radius: 5px;\n background: none;\n cursor: pointer;\n}\n.log__rule-color input[type='color']:disabled {\n opacity: 0.35;\n cursor: default;\n}\n.log__rule-ic {\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n padding: 0;\n color: var(--muted);\n background: none;\n border: 1px solid transparent;\n border-radius: 6px;\n cursor: pointer;\n}\n.log__rule-ic:hover:not(:disabled) {\n color: var(--accent);\n border-color: var(--border-2);\n}\n.log__rule-ic:disabled {\n opacity: 0.3;\n cursor: default;\n}\n.log__rule-del:hover:not(:disabled) {\n color: var(--danger);\n}\n.log__rules-add {\n align-self: flex-start;\n display: inline-flex;\n align-items: center;\n gap: 5px;\n margin-top: 2px;\n padding: 4px 10px;\n font-family: inherit;\n font-size: 12px;\n color: var(--muted);\n background: var(--code-bg);\n border: 1px solid var(--border-2);\n border-radius: 6px;\n cursor: pointer;\n}\n.log__rules-add:hover {\n color: var(--accent);\n border-color: var(--accent);\n}\n\n.log__scroll {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n overscroll-behavior: contain;\n}\n.log__sizer {\n position: relative;\n min-width: 100%;\n}\n.log__row {\n display: flex;\n align-items: flex-start;\n min-width: 100%;\n height: 19px;\n line-height: 19px;\n white-space: nowrap;\n font-family: var(--font-mono);\n font-size: 12.5px;\n}\n.log__row--active {\n background: color-mix(in srgb, var(--accent) 14%, transparent);\n}\n.log__row--context .log__tx {\n opacity: 0.66;\n}\n.log__ln {\n position: sticky;\n left: 0;\n flex-shrink: 0;\n width: 64px;\n padding-right: 12px;\n text-align: right;\n color: var(--faint);\n background: var(--code-bg);\n user-select: none;\n}\n.log__row--active .log__ln {\n background: color-mix(in srgb, var(--accent) 14%, var(--code-bg));\n}\n\n/* fold gutter — a fixed cell right after the line number, present for every row (only when the file has\n stacks) so header/frame/plain text all stay column-aligned. Holds the chevron on headers; draws a faint\n guide line down the left edge of frame rows to read the run as a nested block. */\n.log__fold-cell {\n flex-shrink: 0;\n width: 16px;\n align-self: stretch;\n display: flex;\n align-items: center;\n justify-content: center;\n user-select: none;\n}\n.log__row--frame .log__fold-cell {\n box-shadow: inset 1px 0 0 var(--border-2);\n}\n.log__fold {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 14px;\n height: 14px;\n padding: 0;\n border: none;\n background: none;\n color: var(--faint);\n cursor: pointer;\n}\n.log__chev {\n transition: transform 0.12s ease;\n}\n.log__chev--open {\n transform: rotate(90deg);\n}\n.log__fold:hover .log__chev {\n color: var(--accent);\n}\n\n.log__tx {\n flex: 1 1 auto;\n padding-right: 18px;\n color: var(--text);\n white-space: pre;\n}\n.log__row--header .log__tx {\n color: var(--text);\n font-weight: 600;\n}\n.log__row--frame .log__tx {\n color: var(--muted);\n}\n.log__tx :deep(.log__hit) {\n background: #ffd43b;\n color: #1a1a1a;\n border-radius: 2px;\n}\n\n.log__badge {\n flex-shrink: 0;\n align-self: center;\n margin-left: 8px;\n padding: 0 8px;\n height: 15px;\n font-family: var(--font-mono);\n font-size: 10.5px;\n line-height: 13px;\n color: var(--faint);\n background: var(--surface-2);\n border: 1px solid var(--border-2);\n border-radius: 9px;\n cursor: pointer;\n user-select: none;\n}\n.log__badge:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n\n/* filter-mode context expander bar (one virtual row tall) */\n.log__gaprow {\n position: sticky;\n left: 0;\n align-items: center;\n justify-content: center;\n gap: 10px;\n background: var(--surface-2);\n color: var(--faint);\n user-select: none;\n}\n.log__gapbtn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 15px;\n padding: 0;\n color: var(--muted);\n background: none;\n border: none;\n cursor: pointer;\n}\n.log__gapbtn:hover {\n color: var(--accent);\n}\n.log__gapbtn--up {\n transform: rotate(180deg);\n}\n.log__gapmain {\n padding: 0 10px;\n height: 15px;\n font-family: var(--font-mono);\n font-size: 11px;\n color: var(--faint);\n background: none;\n border: none;\n cursor: pointer;\n}\n.log__gapmain:hover {\n color: var(--accent);\n}\n</style>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"LogViewer.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/LogViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, ref, shallowRef, watch } from 'vue'\nimport { formatBytes } from '@/lib/format'\nimport { hasAnsiSequences, parseAnsi, type AnsiDocument } from '@/lib/ansi'\nimport Icon from '@/components/icons/Icon.vue'\nimport AnsiText from '@/components/AnsiText'\nimport { useLogRules, type ColorRule } from '@/composables/useLogRules'\n\n/**\n * Side-viewer for large text / log files. Fetches the bytes, splits into lines, and renders them with a\n * fixed-height VIRTUAL list — only the visible window (plus a little overscan) is ever in the DOM, so a\n * multi-hundred-MB log scrolls smoothly instead of locking the page (the plain source view would mount one\n * huge highlighted block). No syntax highlight (logs don't need it, and tokenizing 500k lines is infeasible).\n *\n * Routed here for any `.log`, and for any large text file (see FileChip's size gate) so a 50MB `.txt`/`.csv`\n * doesn't freeze the normal viewer. Tools: a sticky line-number gutter, substring search (highlight, match\n * count, prev/next, optional filter-to-matches), go-to-line, plus three log-specific aids:\n *\n * 1. Call-stack folding — error logs from common languages (Java/Kotlin/Scala, JS/Node, Python, C#/.NET,\n * Ruby) are one header line (\"Exception in thread…\", \"Traceback…\", \"Caused by:\") followed by a run of\n * indented frame lines (\" at …\", ' File \"…\"', …). We detect those runs, hang a chevron off the header,\n * and collapse the frames behind a \"N 帧\" badge (collapsed by DEFAULT). A live search force-expands any\n * stack with a hit; filter mode pulls a matched frame's WHOLE stack in (see {@link filtered}).\n * 2. Context expansion (filter mode) — between filtered chunks sits a \"⋯ N 行\" expander bar; click it to\n * progressively reveal hidden lines around a match (a chunk from the top, from the bottom, or all), the\n * way JetBrains' diff reveals collapsed unchanged regions. Revealed lines are tracked in {@link revealed}.\n * 3. Color rules — user-defined substring/regex rules paint matching lines' background/text (see\n * {@link useLogRules}); applied to visible rows only via {@link styleFor}.\n */\nconst props = defineProps<{ url?: string; name: string; size?: number }>()\n\ntype Status = 'loading' | 'gate' | 'ready' | 'error'\nconst status = ref<Status>('loading')\nconst errorMsg = ref('')\nconst lines = ref<string[]>([])\n// Null is the memory-efficient fast path for ordinary logs. ANSI files keep the parsed line tree so SGR state\n// can carry across newlines while the virtual list still mounts only the visible rows.\nconst ansiDocument = shallowRef<AnsiDocument | null>(null)\n\n// Reading the whole file into memory is unavoidable here (the endpoint has no range support); gate the\n// truly huge ones behind a confirm.\nconst SIZE_LIMIT = 64 * 1024 * 1024\nconst LINE_H = 19 // px — MUST match the .log__row CSS height\nconst OVERSCAN = 12\nconst STEP = 10 // lines revealed per directional context-expand click\n\nconst scrollEl = ref<HTMLElement | null>(null)\nconst scrollTop = ref(0)\nconst viewportH = ref(400)\nlet ro: ResizeObserver | null = null\n\n// ---- stack-trace detection ----------------------------------------------------------------------\n// A stack group spans original lines [header .. frameEnd]; `header` is always `frameStart - 1` (the error /\n// \"Caused by:\" / \"Traceback:\" line just above the run), and the collapsible frames are [frameStart .. frameEnd].\ntype Group = { header: number; frameStart: number; frameEnd: number }\n\n// Only fold a run with at least this many frame lines (folding a lone frame saves nothing).\nconst MIN_FRAMES = 2\n\n// Indented stack-frame lines across common languages, matched on the raw line:\n// RE_AT Java/Kotlin/Scala/JS/Node/C#/.NET/PHP \" at pkg.Class.method(File:12)\" / \"at fn (file.js:1:2)\"\n// (requires a '.' or '(' after the symbol so a prose line like \" at 3pm the job ran\" won't match)\n// RE_FILE Python ' File \"/x.py\", line 10, in fn'\n// RE_FROM Ruby \" from a.rb:5:in `m'\"\n// RE_MORE Java \" ... 12 more\"\n// RE_NET C#/.NET async boundary \" --- End of stack trace from previous location ---\"\nconst RE_AT = /^[ \\t]+at\\s+\\S+[.(]/\nconst RE_FILE = /^[ \\t]+File\\s[\"']/\nconst RE_FROM = /^[ \\t]+from\\s+\\S+:\\d+/\nconst RE_MORE = /^[ \\t]*\\.\\.\\.\\s+\\d+\\s+more\\b/\nconst RE_NET = /^[ \\t]+--- End of /\nfunction isFrame(line: string): boolean {\n return (\n RE_AT.test(line) ||\n RE_FILE.test(line) ||\n RE_FROM.test(line) ||\n RE_MORE.test(line) ||\n RE_NET.test(line)\n )\n}\n\n// Group consecutive frame runs. Python frames come in two lines (the `File \"…\"` line plus the indented source\n// line under it) — absorb that trailing source line into the run so it folds with its frame.\nconst groups = computed<Group[]>(() => {\n const src = lines.value\n const N = src.length\n const out: Group[] = []\n let i = 0\n while (i < N) {\n if (!isFrame(src[i])) {\n i++\n continue\n }\n const runStart = i\n let prevFile = RE_FILE.test(src[i])\n i++\n while (i < N) {\n const ln = src[i]\n if (isFrame(ln)) {\n prevFile = RE_FILE.test(ln)\n i++\n } else if (prevFile && /^[ \\t]+\\S/.test(ln)) {\n prevFile = false // Python source line under a `File \"…\"` frame\n i++\n } else break\n }\n const runEnd = i - 1\n // Prefer the (non-blank) plain line just above the run as the header; otherwise treat the run's first line\n // as the header and fold the rest.\n const above = runStart - 1\n const useAbove = above >= 0 && !isFrame(src[above]) && src[above].trim() !== ''\n const frameStart = useAbove ? runStart : runStart + 1\n if (runEnd - frameStart + 1 >= MIN_FRAMES) {\n out.push({ header: frameStart - 1, frameStart, frameEnd: runEnd })\n }\n }\n return out\n})\n\nconst hasGroups = computed(() => groups.value.length > 0)\n\n// origLine → groupId, for header lines only.\nconst headerMap = computed(() => {\n const m = new Map<number, number>()\n groups.value.forEach((g, i) => m.set(g.header, i))\n return m\n})\n\n// The group whose span [header .. frameEnd] contains `orig` (groups are position-sorted & disjoint), or -1.\nfunction groupOf(orig: number): number {\n const gs = groups.value\n let lo = 0\n let hi = gs.length - 1\n while (lo <= hi) {\n const mid = (lo + hi) >> 1\n const g = gs[mid]\n if (orig < g.header) hi = mid - 1\n else if (orig > g.frameEnd) lo = mid + 1\n else return mid\n }\n return -1\n}\n\n// User-expanded group ids (default = absent = collapsed). Reset on each load.\nconst expanded = ref<Set<number>>(new Set())\nconst allExpanded = computed(() => hasGroups.value && expanded.value.size === groups.value.length)\n\nfunction toggleGroup(id: number): void {\n const s = new Set(expanded.value)\n if (s.has(id)) s.delete(id)\n else s.add(id)\n expanded.value = s\n}\nfunction toggleAll(): void {\n expanded.value = allExpanded.value ? new Set() : new Set(groups.value.map((_, i) => i))\n}\n\n// Is this group folded right now? Collapsed unless the user expanded it — OR a live query has a hit inside it\n// (force-expand so the match can't hide behind the fold).\nfunction isCollapsed(id: number): boolean {\n if (expanded.value.has(id)) return false\n if (debounced.value && includedGroups.value.has(id)) return false\n return true\n}\n\n// ---- color rules --------------------------------------------------------------------------------\n\nconst { rules, add: addRule, remove: removeRule, move: moveRule } = useLogRules()\nconst rulesOpen = ref(false)\n\n// Enabled, non-empty rules pre-compiled to a matcher (+ colors). Invalid regex / color-less rules are dropped.\nconst compiledRules = computed(() => {\n const out: { test: (s: string) => boolean; bg: string; fg: string }[] = []\n for (const r of rules.value) {\n if (!r.enabled || !r.pattern) continue\n if (!(r.bgOn || r.fgOn)) continue\n let test: ((s: string) => boolean) | null\n if (r.regex) {\n try {\n const re = new RegExp(r.pattern, r.caseSensitive ? '' : 'i')\n test = (s) => re.test(s)\n } catch {\n test = null // invalid regex → skip until the user fixes it\n }\n } else if (r.caseSensitive) {\n const n = r.pattern\n test = (s) => s.includes(n)\n } else {\n const n = r.pattern.toLowerCase()\n test = (s) => s.toLowerCase().includes(n)\n }\n if (test) out.push({ test, bg: r.bgOn ? r.bg : '', fg: r.fgOn ? r.fg : '' })\n }\n return out\n})\n\n// First matching rule's colors for a line, or null. Called per VISIBLE row only.\nfunction styleFor(text: string): { bg: string; fg: string } | null {\n for (const c of compiledRules.value) {\n if (c.test(text)) return { bg: c.bg, fg: c.fg }\n }\n return null\n}\n\n// A regex rule with a syntax error — used to flag the input red in the editor.\nfunction regexBad(r: ColorRule): boolean {\n if (!r.regex || !r.pattern) return false\n try {\n new RegExp(r.pattern)\n return false\n } catch {\n return true\n }\n}\n\n// ---- search -------------------------------------------------------------------------------------\n\nconst query = ref('')\nconst debounced = ref('')\nlet debounceTimer: ReturnType<typeof setTimeout> | undefined\nwatch(query, (q) => {\n clearTimeout(debounceTimer)\n debounceTimer = setTimeout(() => {\n debounced.value = q\n currentMatch.value = 0\n revealed.value = new Set() // context is relative to the current filter → drop it when the query changes\n }, 160)\n})\n\n// Clear the search box (× button / Esc) — apply immediately instead of waiting on the debounce.\nfunction clearSearch(): void {\n query.value = ''\n clearTimeout(debounceTimer)\n debounced.value = ''\n currentMatch.value = 0\n revealed.value = new Set()\n}\n\nconst filterMode = ref(false)\nconst errorOnly = ref(false)\nconst currentMatch = ref(0)\n\n// Original line indices (0-based) that contain the query.\nconst matchIdx = computed(() => {\n const q = debounced.value.toLowerCase()\n if (!q) return [] as number[]\n const out: number[] = []\n const src = lines.value\n for (let i = 0; i < src.length; i++) {\n if (src[i].toLowerCase().includes(q)) out.push(i)\n }\n return out\n})\n\nfunction hitsQuery(text: string, q: string): boolean {\n return q ? text.toLowerCase().includes(q.toLowerCase()) : false\n}\n\n// Groups with at least one match (header or any frame). Drives both force-expand and filter-keeps-stack.\nconst includedGroups = computed(() => {\n const s = new Set<number>()\n if (!debounced.value) return s\n for (const m of matchIdx.value) {\n const g = groupOf(m)\n if (g >= 0) s.add(g)\n }\n return s\n})\n\n// The currently highlighted match's original line index (for the active-row tint), or -1.\nconst activeOrig = computed(() => {\n const m = matchIdx.value\n return m.length ? m[Math.min(currentMatch.value, m.length - 1)] : -1\n})\n\n// ---- \"only errors\" filter -----------------------------------------------------------------------\n// Error / exception lines for the 只看异常 quick filter. Case-sensitive on purpose: uppercase level tokens,\n// PascalCase *Exception / *Error class names, and a few explicit markers. Lowercase \"error\"/\"warn\" are NOT\n// matched (too noisy in ordinary messages) — a stack's header line is picked up via headerMap instead.\nconst ERROR_RE =\n /\\b(?:ERROR|FATAL|SEVERE|CRITICAL|PANIC|FAILED|FAILURE)\\b|\\b\\w*(?:Exception|Error)\\b|\\bTraceback\\b|\\bCaused by\\b|^\\s*panic:/\n\n// Error-line indices (only computed while the toggle is on). Stack headers always count as errors.\nconst errorIdx = computed<number[]>(() => {\n if (!errorOnly.value) return []\n const src = lines.value\n const hdr = headerMap.value\n const out: number[] = []\n for (let i = 0; i < src.length; i++) {\n if (hdr.has(i) || ERROR_RE.test(src[i])) out.push(i)\n }\n return out\n})\nconst errorSet = computed(() => new Set(errorIdx.value))\n\n// The search \"过滤\" toggle only bites when there's a query.\nconst searchFilterActive = computed(() => filterMode.value && debounced.value !== '')\n\n// The anchor lines the filtered view is built around (null ⇒ no filter → show the whole log via `runs`).\n// Search + errors compose as an intersection (\"errors that also match the query\"); either alone is its own set.\nconst anchors = computed<number[] | null>(() => {\n const s = searchFilterActive.value\n const e = errorOnly.value\n if (!s && !e) return null\n if (s && e) {\n const es = errorSet.value\n return matchIdx.value.filter((i) => es.has(i))\n }\n return e ? errorIdx.value : matchIdx.value\n})\n\n// Keep whole stacks around anchors only when the search filter contributes (its contract: 过滤保留整条堆栈).\n// The pure 只看异常 view stays compact — one line per error, the stack peekable via the context expander.\nconst keepStacks = computed(() => searchFilterActive.value)\n\n// ---- context expansion (filter mode) ------------------------------------------------------------\n// Original line indices the user has manually revealed as context around matches. Reset on load / query change.\nconst revealed = ref<Set<number>>(new Set())\n// Switching filters changes what the gaps mean → drop revealed context.\nwatch([errorOnly, filterMode], () => {\n revealed.value = new Set()\n})\n\n// Reveal a chunk of a gap between shown lines (top, bottom are the shown lines bracketing it; -1 / N at file\n// edges): 'down' from the top match, 'up' toward the bottom match, or 'all'.\nfunction reveal(top: number, bottom: number, dir: 'up' | 'down' | 'all'): void {\n const lo = top + 1\n const hi = bottom - 1\n if (hi < lo) return\n let a = lo\n let b = hi\n if (dir === 'down') b = Math.min(hi, lo + STEP - 1)\n else if (dir === 'up') a = Math.max(lo, hi - STEP + 1)\n const s = new Set(revealed.value)\n for (let i = a; i <= b; i++) s.add(i)\n revealed.value = s\n}\n\n// ---- display layout -----------------------------------------------------------------------------\n// Two modes drive the virtual list:\n// - Non-filter: every line shows, minus the frames of collapsed groups. Expressed as `runs` — the visible\n// original-index segments between the hidden frame ranges — so display↔original stays a cheap binary search\n// (no per-line array; big files stay light).\n// - Filter: only matches show, but every included group contributes its WHOLE span so stacks stay intact,\n// plus any manually-revealed context lines; the remaining gaps become expander rows. Materialized as an\n// item list (bounded by what's shown, normally small).\n\n// Non-filter: visible segments + total row count.\nconst runs = computed(() => {\n const gs = groups.value\n const N = lines.value.length\n const out: { o0: number; o1: number; d0: number }[] = []\n let disp = 0\n let cursor = 0\n for (let id = 0; id < gs.length; id++) {\n if (!isCollapsed(id)) continue\n const g = gs[id]\n if (g.frameStart > cursor) {\n // visible run up to and including the header (header === frameStart - 1)\n out.push({ o0: cursor, o1: g.frameStart - 1, d0: disp })\n disp += g.frameStart - cursor\n }\n cursor = g.frameEnd + 1 // skip the folded frames\n }\n if (cursor < N) {\n out.push({ o0: cursor, o1: N - 1, d0: disp })\n disp += N - cursor\n }\n return { runs: out, total: disp }\n})\n\n// Filter-mode items: `line` rows plus `gap` expander rows. `key` is a parallel, strictly-increasing lookup\n// array (lines → their orig, gaps → top + 0.5) so origToDisplay can binary-search a match's row.\ntype FItem = { t: 'line'; orig: number } | { t: 'gap'; top: number; bottom: number; size: number }\nconst filtered = computed<{ items: FItem[]; key: number[] } | null>(() => {\n const anc = anchors.value\n if (anc === null) return null\n const N = lines.value.length\n const gs = groups.value\n // Stacks kept whole around anchors — only when the search filter contributes (see keepStacks).\n const ranges: [number, number][] = []\n if (keepStacks.value) {\n const inc = new Set<number>()\n for (const a of anc) {\n const g = groupOf(a)\n if (g >= 0) inc.add(g)\n }\n for (const id of inc) ranges.push([gs[id].header, gs[id].frameEnd])\n ranges.sort((a, b) => a[0] - b[0])\n }\n const covered = (o: number): boolean => {\n let lo = 0\n let hi = ranges.length - 1\n while (lo <= hi) {\n const mid = (lo + hi) >> 1\n if (o < ranges[mid][0]) hi = mid - 1\n else if (o > ranges[mid][1]) lo = mid + 1\n else return true\n }\n return false\n }\n // Standalone shown lines = anchors + revealed context, minus whatever a kept-whole stack already covers.\n const extra: number[] = []\n for (const a of anc) if (!covered(a)) extra.push(a)\n for (const r of revealed.value) if (!covered(r)) extra.push(r)\n extra.sort((a, b) => a - b)\n // Merge (sorted) range indices with (sorted) standalone lines → sorted, de-duped base set.\n const base: number[] = []\n let ri = 0\n let si = 0\n let cur = ranges.length ? ranges[0][0] : Infinity\n let last = -1\n while (ri < ranges.length || si < extra.length) {\n const rn = ri < ranges.length ? cur : Infinity\n const sn = si < extra.length ? extra[si] : Infinity\n let v: number\n if (rn <= sn) {\n v = rn\n if (cur >= ranges[ri][1]) {\n ri++\n cur = ri < ranges.length ? ranges[ri][0] : Infinity\n } else cur++\n } else {\n v = sn\n si++\n }\n if (v !== last) {\n base.push(v)\n last = v\n }\n }\n // Weave in gap expanders between non-adjacent shown lines (and at the file's head/tail).\n const items: FItem[] = []\n const key: number[] = []\n let prev = -1\n for (const o of base) {\n if (o - prev > 1) {\n items.push({ t: 'gap', top: prev, bottom: o, size: o - prev - 1 })\n key.push(prev + 0.5)\n }\n items.push({ t: 'line', orig: o })\n key.push(o)\n prev = o\n }\n if (prev < N - 1) {\n items.push({ t: 'gap', top: prev, bottom: N, size: N - 1 - prev })\n key.push(prev + 0.5)\n }\n return { items, key }\n})\n\nconst inFilter = computed(() => filtered.value !== null)\nconst total = computed(() => (filtered.value ? filtered.value.items.length : runs.value.total))\n\ntype RowKind = 'plain' | 'header' | 'frame'\ntype RowInfo = {\n n: number\n text: string\n kind: RowKind\n collapsed: boolean\n count: number\n gid: number\n}\n\nfunction rowInfo(orig: number): RowInfo {\n const text = lines.value[orig] ?? ''\n const gid = headerMap.value.get(orig)\n if (gid !== undefined) {\n const g = groups.value[gid]\n // Filter mode always shows groups expanded (frames must be visible to keep the stack), so no fold chrome.\n return {\n n: orig + 1,\n text,\n kind: 'header',\n collapsed: filtered.value ? false : isCollapsed(gid),\n count: g.frameEnd - g.frameStart + 1,\n gid,\n }\n }\n const fg = groupOf(orig)\n const kind: RowKind = fg >= 0 && orig >= groups.value[fg].frameStart ? 'frame' : 'plain'\n return { n: orig + 1, text, kind, collapsed: false, count: 0, gid: -1 }\n}\n\n// Non-filter: display row index → original line index (binary search over the visible runs).\nfunction origInRuns(rs: { o0: number; o1: number; d0: number }[], d: number): number {\n let lo = 0\n let hi = rs.length - 1\n let pick = 0\n while (lo <= hi) {\n const mid = (lo + hi) >> 1\n if (d < rs[mid].d0) hi = mid - 1\n else {\n pick = mid\n lo = mid + 1\n }\n }\n const r = rs[pick]\n return r ? r.o0 + (d - r.d0) : d\n}\n\nconst startIdx = computed(() => Math.max(0, Math.floor(scrollTop.value / LINE_H) - OVERSCAN))\nconst endIdx = computed(() =>\n Math.min(total.value, Math.ceil((scrollTop.value + viewportH.value) / LINE_H) + OVERSCAN),\n)\n\n// One flat shape for every visible row (content OR gap) so the template needs no union narrowing. Content\n// rows default the gap fields; gap rows default the content fields.\ntype VisRow = {\n i: number\n isGap: boolean\n n: number\n text: string\n kind: RowKind\n collapsed: boolean\n count: number\n gid: number\n style: { bg: string; fg: string } | null\n context: boolean\n top: number\n bottom: number\n size: number\n step: number\n}\nfunction contentRow(i: number, orig: number): VisRow {\n const info = rowInfo(orig)\n return {\n i,\n isGap: false,\n ...info,\n style: styleFor(info.text),\n // \"Context\" dimming is a filter-mode cue for manually-revealed lines; never dim in the full (non-filter)\n // view, where leftover `revealed` entries are irrelevant.\n context:\n filtered.value !== null && revealed.value.has(orig) && !hitsQuery(info.text, debounced.value),\n top: 0,\n bottom: 0,\n size: 0,\n step: 0,\n }\n}\nconst visible = computed<VisRow[]>(() => {\n const rows: VisRow[] = []\n const s = startIdx.value\n const e = endIdx.value\n const f = filtered.value\n if (f) {\n for (let i = s; i < e; i++) {\n const it = f.items[i]\n if (!it) continue\n if (it.t === 'gap') {\n rows.push({\n i,\n isGap: true,\n n: 0,\n text: '',\n kind: 'plain',\n collapsed: false,\n count: 0,\n gid: -1,\n style: null,\n context: false,\n top: it.top,\n bottom: it.bottom,\n size: it.size,\n step: Math.min(STEP, it.size),\n })\n } else {\n rows.push(contentRow(i, it.orig))\n }\n }\n } else {\n const rs = runs.value.runs\n for (let i = s; i < e; i++) rows.push(contentRow(i, origInRuns(rs, i)))\n }\n return rows\n})\n\n// ---- navigation ---------------------------------------------------------------------------------\n\nfunction scrollToDisplay(i: number): void {\n const el = scrollEl.value\n if (el) el.scrollTop = Math.max(0, i * LINE_H - viewportH.value / 2 + LINE_H)\n}\n\n// Original line index → current display row index (or -1 if not shown). Folded frames redirect to their header.\nfunction origToDisplay(orig: number): number {\n const f = filtered.value\n if (f) {\n const k = f.key\n let lo = 0\n let hi = k.length - 1\n while (lo <= hi) {\n const mid = (lo + hi) >> 1\n if (k[mid] === orig) return mid\n if (k[mid] < orig) lo = mid + 1\n else hi = mid - 1\n }\n return -1\n }\n const g = groupOf(orig)\n if (g >= 0 && orig >= groups.value[g].frameStart && isCollapsed(g)) orig = groups.value[g].header\n const rs = runs.value.runs\n let lo = 0\n let hi = rs.length - 1\n while (lo <= hi) {\n const mid = (lo + hi) >> 1\n if (orig < rs[mid].o0) hi = mid - 1\n else if (orig > rs[mid].o1) lo = mid + 1\n else return rs[mid].d0 + (orig - rs[mid].o0)\n }\n return -1\n}\n\nfunction gotoMatch(delta: number): void {\n const m = matchIdx.value\n if (!m.length) return\n currentMatch.value = (currentMatch.value + delta + m.length) % m.length\n const d = origToDisplay(m[currentMatch.value])\n if (d >= 0) scrollToDisplay(d)\n}\n\nconst gotoLineInput = ref('')\nfunction gotoLine(): void {\n const n = parseInt(gotoLineInput.value, 10)\n if (!Number.isFinite(n)) return\n const orig = Math.max(0, Math.min(lines.value.length - 1, n - 1))\n const d = origToDisplay(orig)\n if (d >= 0) scrollToDisplay(d)\n}\n\nfunction onScroll(): void {\n if (scrollEl.value) scrollTop.value = scrollEl.value.scrollTop\n}\n\n// ---- load ---------------------------------------------------------------------------------------\n\nasync function doLoad(): Promise<void> {\n if (!props.url) return\n status.value = 'loading'\n errorMsg.value = ''\n ansiDocument.value = null\n try {\n const res = await fetch(props.url)\n if (!res.ok) throw new Error(`HTTP ${res.status}`)\n const text = await res.text()\n const parsed = hasAnsiSequences(text) ? parseAnsi(text) : null\n const loadedLines = parsed ? [...parsed.lines] : text.split('\\n')\n // Drop a single trailing empty line (file ended with \\n) so there's no phantom last row.\n if (loadedLines.length > 1) {\n const last = loadedLines[loadedLines.length - 1]\n if (typeof last === 'string' ? last === '' : last.text === '') loadedLines.pop()\n }\n if (parsed) {\n ansiDocument.value = {\n ...parsed,\n lines: loadedLines as typeof parsed.lines,\n text: (loadedLines as typeof parsed.lines).map((line) => line.text).join('\\n'),\n }\n lines.value = ansiDocument.value.lines.map((line) => line.text)\n } else lines.value = loadedLines as string[]\n expanded.value = new Set() // stacks fold by default on each fresh file\n revealed.value = new Set()\n scrollTop.value = 0\n if (scrollEl.value) scrollEl.value.scrollTop = 0\n status.value = 'ready'\n } catch (e) {\n errorMsg.value = e instanceof Error ? e.message : String(e)\n status.value = 'error'\n }\n}\n\nfunction load(): void {\n // A reused component must not expose the previous file through the header's Copy action while the next file\n // is gated/loading.\n lines.value = []\n ansiDocument.value = null\n if (props.size && props.size > SIZE_LIMIT) {\n status.value = 'gate'\n return\n }\n void doLoad()\n}\n\n// scrollEl lives inside the `status === 'ready'` branch (a v-else), so it does NOT exist at mount time —\n// it appears only after the file finishes loading. Watch the ref and attach the ResizeObserver when the\n// element shows up (and re-attach if the pane is torn down + rebuilt on reload), so viewportH reflects the\n// REAL pane height. Without this it stayed at the 400px default and only ~half a screen of rows rendered,\n// leaving the rest blank until you scrolled. flush:'post' so clientHeight is read after the DOM is painted.\nwatch(\n scrollEl,\n (el) => {\n ro?.disconnect()\n if (el && typeof ResizeObserver !== 'undefined') {\n ro = new ResizeObserver(() => {\n if (scrollEl.value) viewportH.value = scrollEl.value.clientHeight\n })\n ro.observe(el)\n viewportH.value = el.clientHeight\n }\n },\n { flush: 'post' },\n)\nwatch(() => props.url, load, { immediate: true })\nonBeforeUnmount(() => {\n clearTimeout(debounceTimer)\n ro?.disconnect()\n})\ndefineExpose({ copyText: () => lines.value.join('\\n') })\n</script>\n\n<template>\n <div class=\"log\">\n <p v-if=\"status === 'loading'\" class=\"log__msg\">正在加载…</p>\n\n <div v-else-if=\"status === 'gate'\" class=\"log__msg log__gate\">\n <p>该文件较大({{ formatBytes(props.size) }}),将在浏览器内加载。是否继续?</p>\n <button type=\"button\" class=\"log__btn\" @click=\"doLoad\">加载并预览</button>\n </div>\n\n <div v-else-if=\"status === 'error'\" class=\"log__msg log__msg--error\">\n <p>加载失败:{{ errorMsg }}</p>\n <button type=\"button\" class=\"log__btn\" @click=\"doLoad\">重试</button>\n </div>\n\n <template v-else>\n <!-- toolbar: search + match nav + filter + fold-all + color rules + go-to-line -->\n <div class=\"log__bar\">\n <div class=\"log__searchwrap\">\n <input\n v-model=\"query\"\n class=\"log__search\"\n type=\"text\"\n placeholder=\"搜索…\"\n aria-label=\"搜索\"\n @keydown.esc=\"clearSearch\"\n />\n <button\n v-if=\"query\"\n type=\"button\"\n class=\"log__clear\"\n title=\"清空搜索\"\n aria-label=\"清空搜索\"\n @click=\"clearSearch\"\n >\n <Icon name=\"close\" :size=\"12\" />\n </button>\n </div>\n <span v-if=\"debounced\" class=\"log__matches\">\n {{ matchIdx.length ? currentMatch + 1 : 0 }}/{{ matchIdx.length }}\n </span>\n <button\n type=\"button\"\n class=\"log__nav\"\n title=\"上一处\"\n :disabled=\"!matchIdx.length\"\n @click=\"gotoMatch(-1)\"\n >\n ↑\n </button>\n <button\n type=\"button\"\n class=\"log__nav\"\n title=\"下一处\"\n :disabled=\"!matchIdx.length\"\n @click=\"gotoMatch(1)\"\n >\n ↓\n </button>\n <button\n type=\"button\"\n class=\"log__nav log__nav--text\"\n :class=\"{ 'log__nav--on': filterMode }\"\n :disabled=\"!debounced\"\n title=\"只看匹配行(命中的堆栈整条保留,行间可展开上下文)\"\n @click=\"filterMode = !filterMode\"\n >\n 过滤\n </button>\n <button\n type=\"button\"\n class=\"log__nav log__nav--text\"\n :class=\"{ 'log__nav--on': errorOnly }\"\n title=\"只显示异常/错误行(异常头、ERROR/FATAL、*Exception/*Error 等;可与搜索过滤叠加)\"\n @click=\"errorOnly = !errorOnly\"\n >\n 只看异常\n </button>\n <button\n v-if=\"hasGroups\"\n type=\"button\"\n class=\"log__nav log__nav--text\"\n :class=\"{ 'log__nav--on': allExpanded }\"\n :title=\"`共 ${groups.length} 处调用堆栈,点击${allExpanded ? '折叠' : '展开'}全部`\"\n @click=\"toggleAll\"\n >\n {{ allExpanded ? '折叠堆栈' : '展开堆栈' }}\n </button>\n <button\n type=\"button\"\n class=\"log__nav log__nav--text\"\n :class=\"{ 'log__nav--on': rulesOpen || compiledRules.length > 0 }\"\n title=\"按规则给行上色\"\n @click=\"rulesOpen = !rulesOpen\"\n >\n 上色\n </button>\n <span class=\"log__spacer\" />\n <input\n v-model=\"gotoLineInput\"\n class=\"log__goto\"\n type=\"text\"\n inputmode=\"numeric\"\n placeholder=\"行号\"\n aria-label=\"跳转到行\"\n @keydown.enter=\"gotoLine\"\n />\n <span class=\"log__total\">\n {{ lines.length }} 行<template v-if=\"hasGroups\"> · {{ groups.length }} 堆栈</template>\n </span>\n\n <!-- color-rules editor (dropdown). Backdrop closes on outside click. -->\n <div v-if=\"rulesOpen\" class=\"log__overlay\" @click=\"rulesOpen = false\" />\n <div v-if=\"rulesOpen\" class=\"log__rules\" @click.stop>\n <div class=\"log__rules-head\">\n <span class=\"log__rules-title\">行上色规则</span>\n <span class=\"log__rules-hint\">从上到下,首个匹配生效</span>\n </div>\n <p v-if=\"!rules.length\" class=\"log__rules-empty\">\n 还没有规则。添加一条,用关键字或正则给匹配行上色(如 <code>ERROR</code> 标红)。\n </p>\n <div v-for=\"(r, idx) in rules\" :key=\"r.id\" class=\"log__rule\">\n <input v-model=\"r.enabled\" type=\"checkbox\" title=\"启用该规则\" />\n <input\n v-model=\"r.pattern\"\n class=\"log__rule-pat\"\n :class=\"{ 'log__rule-pat--bad': regexBad(r) }\"\n type=\"text\"\n placeholder=\"关键字或正则\"\n />\n <button\n type=\"button\"\n class=\"log__rule-tog\"\n :class=\"{ 'log__rule-tog--on': r.regex }\"\n title=\"按正则匹配\"\n @click=\"r.regex = !r.regex\"\n >\n .*\n </button>\n <button\n type=\"button\"\n class=\"log__rule-tog\"\n :class=\"{ 'log__rule-tog--on': r.caseSensitive }\"\n title=\"区分大小写\"\n @click=\"r.caseSensitive = !r.caseSensitive\"\n >\n Aa\n </button>\n <label class=\"log__rule-color\" title=\"背景色\">\n <input v-model=\"r.bgOn\" type=\"checkbox\" />\n <input v-model=\"r.bg\" type=\"color\" :disabled=\"!r.bgOn\" />\n <span>底</span>\n </label>\n <label class=\"log__rule-color\" title=\"字体色\">\n <input v-model=\"r.fgOn\" type=\"checkbox\" />\n <input v-model=\"r.fg\" type=\"color\" :disabled=\"!r.fgOn\" />\n <span>字</span>\n </label>\n <button\n type=\"button\"\n class=\"log__rule-ic\"\n title=\"上移\"\n :disabled=\"idx === 0\"\n @click=\"moveRule(r.id, -1)\"\n >\n <Icon name=\"arrowUp\" :size=\"12\" />\n </button>\n <button\n type=\"button\"\n class=\"log__rule-ic\"\n title=\"下移\"\n :disabled=\"idx === rules.length - 1\"\n @click=\"moveRule(r.id, 1)\"\n >\n <Icon name=\"arrowDown\" :size=\"12\" />\n </button>\n <button\n type=\"button\"\n class=\"log__rule-ic log__rule-del\"\n title=\"删除\"\n @click=\"removeRule(r.id)\"\n >\n <Icon name=\"trash\" :size=\"12\" />\n </button>\n </div>\n <button type=\"button\" class=\"log__rules-add\" @click=\"addRule\">\n <Icon name=\"plus\" :size=\"12\" /> 添加规则\n </button>\n </div>\n </div>\n\n <!-- virtual list: a fixed-height sizer drives the scrollbar; only the visible window is mounted -->\n <div ref=\"scrollEl\" class=\"log__scroll\" @scroll=\"onScroll\">\n <div class=\"log__sizer\" :style=\"{ height: total * LINE_H + 'px' }\">\n <div class=\"log__rows\" :style=\"{ paddingTop: startIdx * LINE_H + 'px' }\">\n <div\n v-for=\"row in visible\"\n :key=\"row.i\"\n class=\"log__row\"\n :class=\"{\n log__gaprow: row.isGap,\n 'log__row--active': !row.isGap && row.n - 1 === activeOrig,\n 'log__row--header': !row.isGap && row.kind === 'header',\n 'log__row--frame': !row.isGap && row.kind === 'frame',\n 'log__row--context': row.context,\n }\"\n :style=\"\n !row.isGap && row.style && row.style.bg ? { background: row.style.bg } : undefined\n \"\n >\n <!-- gap expander (filter mode): reveal hidden context around matches -->\n <template v-if=\"row.isGap\">\n <button\n v-if=\"row.size > STEP\"\n type=\"button\"\n class=\"log__gapbtn\"\n :title=\"`向下展开 ${row.step} 行(上方匹配之后)`\"\n @click=\"reveal(row.top, row.bottom, 'down')\"\n >\n <Icon name=\"chevronDown\" :size=\"12\" />\n </button>\n <button\n type=\"button\"\n class=\"log__gapmain\"\n :title=\"`展开全部 ${row.size} 行`\"\n @click=\"reveal(row.top, row.bottom, 'all')\"\n >\n ⋯ {{ row.size }} 行\n </button>\n <button\n v-if=\"row.size > STEP\"\n type=\"button\"\n class=\"log__gapbtn log__gapbtn--up\"\n :title=\"`向上展开 ${row.step} 行(下方匹配之前)`\"\n @click=\"reveal(row.top, row.bottom, 'up')\"\n >\n <Icon name=\"chevronDown\" :size=\"12\" />\n </button>\n </template>\n\n <!-- content row -->\n <template v-else>\n <span class=\"log__ln\">{{ row.n }}</span>\n <span v-if=\"hasGroups\" class=\"log__fold-cell\">\n <button\n v-if=\"row.kind === 'header' && !inFilter\"\n type=\"button\"\n class=\"log__fold\"\n :title=\"row.collapsed ? '展开堆栈' : '折叠堆栈'\"\n @click=\"toggleGroup(row.gid)\"\n >\n <Icon\n name=\"chevronRight\"\n :size=\"12\"\n class=\"log__chev\"\n :class=\"{ 'log__chev--open': !row.collapsed }\"\n />\n </button>\n </span>\n <span\n class=\"log__tx\"\n :style=\"row.style && row.style.fg ? { color: row.style.fg } : undefined\"\n >\n <AnsiText\n :line=\"ansiDocument?.lines[row.n - 1] ?? row.text\"\n :query=\"debounced\"\n mark-class=\"log__hit\"\n />\n </span>\n <button\n v-if=\"row.kind === 'header' && row.collapsed && !inFilter\"\n type=\"button\"\n class=\"log__badge\"\n title=\"展开堆栈\"\n @click=\"toggleGroup(row.gid)\"\n >\n ⋯ {{ row.count }} 帧\n </button>\n </template>\n </div>\n </div>\n </div>\n </div>\n </template>\n </div>\n</template>\n\n<style scoped>\n.log {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n background: var(--code-bg);\n}\n\n.log__msg {\n padding: 20px 16px;\n color: var(--muted);\n font-size: 13px;\n}\n.log__msg--error {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 10px;\n color: var(--danger);\n}\n.log__gate {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 12px;\n}\n.log__btn {\n font-family: inherit;\n font-size: 12.5px;\n color: var(--text);\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 8px;\n padding: 5px 12px;\n cursor: pointer;\n}\n.log__btn:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n\n.log__bar {\n position: relative;\n display: flex;\n align-items: center;\n gap: 6px;\n flex-shrink: 0;\n padding: 8px 12px;\n border-bottom: 1px solid var(--border);\n background: var(--surface);\n}\n.log__searchwrap {\n position: relative;\n flex: 1 1 auto;\n min-width: 60px;\n display: flex;\n}\n.log__search {\n flex: 1 1 auto;\n min-width: 0;\n padding: 5px 28px 5px 10px;\n font-family: inherit;\n font-size: 12.5px;\n color: var(--text);\n background: var(--code-bg);\n border: 1px solid var(--border-2);\n border-radius: 7px;\n}\n.log__search:focus,\n.log__goto:focus {\n outline: none;\n border-color: var(--accent);\n}\n.log__clear {\n position: absolute;\n top: 50%;\n right: 5px;\n transform: translateY(-50%);\n display: flex;\n align-items: center;\n justify-content: center;\n width: 19px;\n height: 19px;\n padding: 0;\n color: var(--faint);\n background: none;\n border: none;\n border-radius: 5px;\n cursor: pointer;\n}\n.log__clear:hover {\n color: var(--text);\n background: var(--surface-2);\n}\n.log__matches {\n flex-shrink: 0;\n font-family: var(--font-mono);\n font-size: 11.5px;\n color: var(--faint);\n min-width: 38px;\n text-align: center;\n}\n.log__nav {\n flex-shrink: 0;\n min-width: 26px;\n height: 26px;\n padding: 0 6px;\n font-family: inherit;\n font-size: 12px;\n color: var(--muted);\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 6px;\n cursor: pointer;\n}\n.log__nav:hover:not(:disabled) {\n border-color: var(--accent);\n color: var(--accent);\n}\n.log__nav:disabled {\n opacity: 0.4;\n cursor: default;\n}\n.log__nav--on {\n color: var(--accent);\n border-color: var(--accent);\n background: var(--surface-2);\n}\n.log__spacer {\n flex: 1 1 auto;\n}\n.log__goto {\n flex-shrink: 0;\n width: 64px;\n padding: 5px 8px;\n font-family: var(--font-mono);\n font-size: 12px;\n color: var(--text);\n background: var(--code-bg);\n border: 1px solid var(--border-2);\n border-radius: 7px;\n}\n.log__total {\n flex-shrink: 0;\n font-size: 11.5px;\n color: var(--faint);\n white-space: nowrap;\n}\n\n/* color-rules dropdown */\n.log__overlay {\n position: fixed;\n inset: 0;\n z-index: 40;\n background: transparent;\n}\n.log__rules {\n position: absolute;\n z-index: 41;\n top: calc(100% + 4px);\n right: 12px;\n width: min(520px, calc(100vw - 24px));\n max-height: 320px;\n overflow: auto;\n padding: 10px;\n display: flex;\n flex-direction: column;\n gap: 6px;\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 10px;\n box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);\n}\n.log__rules-head {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 8px;\n}\n.log__rules-title {\n font-size: 12.5px;\n font-weight: 600;\n color: var(--text);\n}\n.log__rules-hint {\n font-size: 11px;\n color: var(--faint);\n}\n.log__rules-empty {\n font-size: 12px;\n color: var(--muted);\n line-height: 1.5;\n margin: 2px 0;\n}\n.log__rules-empty code {\n font-family: var(--font-mono);\n font-size: 11px;\n padding: 0 4px;\n background: var(--code-bg);\n border-radius: 4px;\n}\n.log__rule {\n display: flex;\n align-items: center;\n gap: 5px;\n}\n.log__rule-pat {\n flex: 1 1 auto;\n min-width: 80px;\n padding: 4px 8px;\n font-family: var(--font-mono);\n font-size: 12px;\n color: var(--text);\n background: var(--code-bg);\n border: 1px solid var(--border-2);\n border-radius: 6px;\n}\n.log__rule-pat:focus {\n outline: none;\n border-color: var(--accent);\n}\n.log__rule-pat--bad {\n border-color: var(--danger);\n color: var(--danger);\n}\n.log__rule-tog {\n flex-shrink: 0;\n width: 26px;\n height: 26px;\n padding: 0;\n font-family: var(--font-mono);\n font-size: 11px;\n color: var(--muted);\n background: var(--code-bg);\n border: 1px solid var(--border-2);\n border-radius: 6px;\n cursor: pointer;\n}\n.log__rule-tog--on {\n color: var(--accent);\n border-color: var(--accent);\n background: var(--surface-2);\n}\n.log__rule-color {\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n gap: 3px;\n font-size: 11px;\n color: var(--faint);\n cursor: pointer;\n}\n.log__rule-color input[type='color'] {\n width: 22px;\n height: 22px;\n padding: 0;\n border: 1px solid var(--border-2);\n border-radius: 5px;\n background: none;\n cursor: pointer;\n}\n.log__rule-color input[type='color']:disabled {\n opacity: 0.35;\n cursor: default;\n}\n.log__rule-ic {\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n padding: 0;\n color: var(--muted);\n background: none;\n border: 1px solid transparent;\n border-radius: 6px;\n cursor: pointer;\n}\n.log__rule-ic:hover:not(:disabled) {\n color: var(--accent);\n border-color: var(--border-2);\n}\n.log__rule-ic:disabled {\n opacity: 0.3;\n cursor: default;\n}\n.log__rule-del:hover:not(:disabled) {\n color: var(--danger);\n}\n.log__rules-add {\n align-self: flex-start;\n display: inline-flex;\n align-items: center;\n gap: 5px;\n margin-top: 2px;\n padding: 4px 10px;\n font-family: inherit;\n font-size: 12px;\n color: var(--muted);\n background: var(--code-bg);\n border: 1px solid var(--border-2);\n border-radius: 6px;\n cursor: pointer;\n}\n.log__rules-add:hover {\n color: var(--accent);\n border-color: var(--accent);\n}\n\n.log__scroll {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n overscroll-behavior: contain;\n}\n.log__sizer {\n position: relative;\n min-width: 100%;\n}\n.log__row {\n display: flex;\n align-items: flex-start;\n min-width: 100%;\n height: 19px;\n line-height: 19px;\n white-space: nowrap;\n font-family: var(--font-mono);\n font-size: 12.5px;\n}\n.log__row--active {\n background: color-mix(in srgb, var(--accent) 14%, transparent);\n}\n.log__row--context .log__tx {\n opacity: 0.66;\n}\n.log__ln {\n position: sticky;\n left: 0;\n flex-shrink: 0;\n width: 64px;\n padding-right: 12px;\n text-align: right;\n color: var(--faint);\n background: var(--code-bg);\n user-select: none;\n}\n.log__row--active .log__ln {\n background: color-mix(in srgb, var(--accent) 14%, var(--code-bg));\n}\n\n/* fold gutter — a fixed cell right after the line number, present for every row (only when the file has\n stacks) so header/frame/plain text all stay column-aligned. Holds the chevron on headers; draws a faint\n guide line down the left edge of frame rows to read the run as a nested block. */\n.log__fold-cell {\n flex-shrink: 0;\n width: 16px;\n align-self: stretch;\n display: flex;\n align-items: center;\n justify-content: center;\n user-select: none;\n}\n.log__row--frame .log__fold-cell {\n box-shadow: inset 1px 0 0 var(--border-2);\n}\n.log__fold {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 14px;\n height: 14px;\n padding: 0;\n border: none;\n background: none;\n color: var(--faint);\n cursor: pointer;\n}\n.log__chev {\n transition: transform 0.12s ease;\n}\n.log__chev--open {\n transform: rotate(90deg);\n}\n.log__fold:hover .log__chev {\n color: var(--accent);\n}\n\n.log__tx {\n flex: 1 1 auto;\n padding-right: 18px;\n color: var(--text);\n white-space: pre;\n}\n.log__row--header .log__tx {\n color: var(--text);\n font-weight: 600;\n}\n.log__row--frame .log__tx {\n color: var(--muted);\n}\n.log__tx :deep(.log__hit) {\n background: #ffd43b;\n color: #1a1a1a;\n border-radius: 2px;\n}\n\n.log__badge {\n flex-shrink: 0;\n align-self: center;\n margin-left: 8px;\n padding: 0 8px;\n height: 15px;\n font-family: var(--font-mono);\n font-size: 10.5px;\n line-height: 13px;\n color: var(--faint);\n background: var(--surface-2);\n border: 1px solid var(--border-2);\n border-radius: 9px;\n cursor: pointer;\n user-select: none;\n}\n.log__badge:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n\n/* filter-mode context expander bar (one virtual row tall) */\n.log__gaprow {\n position: sticky;\n left: 0;\n align-items: center;\n justify-content: center;\n gap: 10px;\n background: var(--surface-2);\n color: var(--faint);\n user-select: none;\n}\n.log__gapbtn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 15px;\n padding: 0;\n color: var(--muted);\n background: none;\n border: none;\n cursor: pointer;\n}\n.log__gapbtn:hover {\n color: var(--accent);\n}\n.log__gapbtn--up {\n transform: rotate(180deg);\n}\n.log__gapmain {\n padding: 0 10px;\n height: 15px;\n font-family: var(--font-mono);\n font-size: 11px;\n color: var(--faint);\n background: none;\n border: none;\n cursor: pointer;\n}\n.log__gapmain:hover {\n color: var(--accent);\n}\n</style>\n"],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Safe, dependency-free ANSI/ECMA-48 text parser for static console transcripts.
|
|
3
|
+
*
|
|
4
|
+
* Only SGR (Select Graphic Rendition, CSI … m) changes presentation. Other terminal control sequences are
|
|
5
|
+
* consumed without being executed: cursor movement / erase commands have no useful meaning in a static file,
|
|
6
|
+
* while OSC hyperlinks and device-control strings must never become active page content. The result is plain
|
|
7
|
+
* text plus a small whitelist of CSS properties, so callers can render it with normal Vue text nodes.
|
|
8
|
+
*/
|
|
9
|
+
export interface AnsiTextStyle {
|
|
10
|
+
color?: string;
|
|
11
|
+
backgroundColor?: string;
|
|
12
|
+
fontWeight?: '700';
|
|
13
|
+
fontStyle?: 'italic';
|
|
14
|
+
opacity?: number;
|
|
15
|
+
textDecorationLine?: string;
|
|
16
|
+
textDecorationStyle?: 'double';
|
|
17
|
+
visibility?: 'hidden';
|
|
18
|
+
}
|
|
19
|
+
export interface AnsiSegment {
|
|
20
|
+
text: string;
|
|
21
|
+
style?: AnsiTextStyle;
|
|
22
|
+
}
|
|
23
|
+
export interface AnsiLine {
|
|
24
|
+
text: string;
|
|
25
|
+
segments: AnsiSegment[];
|
|
26
|
+
}
|
|
27
|
+
export interface AnsiDocument {
|
|
28
|
+
/** True when at least one ANSI/C1 control sequence was consumed. */
|
|
29
|
+
hasAnsi: boolean;
|
|
30
|
+
/** Control-free text, with CR/LF variants normalized to `\n`. */
|
|
31
|
+
text: string;
|
|
32
|
+
lines: AnsiLine[];
|
|
33
|
+
}
|
|
34
|
+
export interface AnsiRenderPiece extends AnsiSegment {
|
|
35
|
+
marked: boolean;
|
|
36
|
+
}
|
|
37
|
+
/** Fast gate used by the viewers so ordinary large text does not allocate a segment tree. */
|
|
38
|
+
export declare function hasAnsiSequences(text: string): boolean;
|
|
39
|
+
/** Parse ANSI presentation and return only inert text + validated styles. */
|
|
40
|
+
export declare function parseAnsi(text: string): AnsiDocument;
|
|
41
|
+
/** Plain text for search/copy/folding; avoids parsing the overwhelmingly common no-ANSI path. */
|
|
42
|
+
export declare function ansiPlainText(text: string): string;
|
|
43
|
+
/** Split one parsed (or ordinary) line around a case-insensitive search query without losing ANSI styling. */
|
|
44
|
+
export declare function ansiRenderPieces(line: AnsiLine | string, query?: string): AnsiRenderPiece[];
|
package/dist/lib/ansi.js
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
//#region src/lib/ansi.ts
|
|
2
|
+
var e = "\x1B", t = "", n = "", r = "", i = [
|
|
3
|
+
"var(--ansi-black, #24292f)",
|
|
4
|
+
"var(--ansi-red, #cf222e)",
|
|
5
|
+
"var(--ansi-green, #116329)",
|
|
6
|
+
"var(--ansi-yellow, #9a6700)",
|
|
7
|
+
"var(--ansi-blue, #0969da)",
|
|
8
|
+
"var(--ansi-magenta, #8250df)",
|
|
9
|
+
"var(--ansi-cyan, #1b7c83)",
|
|
10
|
+
"var(--ansi-white, #57606a)",
|
|
11
|
+
"var(--ansi-bright-black, #57606a)",
|
|
12
|
+
"var(--ansi-bright-red, #a40e26)",
|
|
13
|
+
"var(--ansi-bright-green, #1a7f37)",
|
|
14
|
+
"var(--ansi-bright-yellow, #bf8700)",
|
|
15
|
+
"var(--ansi-bright-blue, #218bff)",
|
|
16
|
+
"var(--ansi-bright-magenta, #a475f9)",
|
|
17
|
+
"var(--ansi-bright-cyan, #3192aa)",
|
|
18
|
+
"var(--ansi-bright-white, #6e7781)"
|
|
19
|
+
];
|
|
20
|
+
function a() {
|
|
21
|
+
return {
|
|
22
|
+
foreground: void 0,
|
|
23
|
+
background: void 0,
|
|
24
|
+
bold: !1,
|
|
25
|
+
dim: !1,
|
|
26
|
+
italic: !1,
|
|
27
|
+
underline: !1,
|
|
28
|
+
doubleUnderline: !1,
|
|
29
|
+
strike: !1,
|
|
30
|
+
overline: !1,
|
|
31
|
+
inverse: !1,
|
|
32
|
+
conceal: !1
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function o(e) {
|
|
36
|
+
for (let t = 0; t < e.length; t++) {
|
|
37
|
+
let n = e.charCodeAt(t);
|
|
38
|
+
if (n === 27 || n >= 128 && n <= 159) return !0;
|
|
39
|
+
}
|
|
40
|
+
return !1;
|
|
41
|
+
}
|
|
42
|
+
function s(e) {
|
|
43
|
+
return e !== void 0 && Number.isInteger(e) && e >= 0 && e <= 255;
|
|
44
|
+
}
|
|
45
|
+
function c(e) {
|
|
46
|
+
if (!s(e)) return;
|
|
47
|
+
if (e < 16) return i[e];
|
|
48
|
+
if (e < 232) {
|
|
49
|
+
let t = e - 16, n = [
|
|
50
|
+
0,
|
|
51
|
+
95,
|
|
52
|
+
135,
|
|
53
|
+
175,
|
|
54
|
+
215,
|
|
55
|
+
255
|
|
56
|
+
];
|
|
57
|
+
return `rgb(${n[Math.floor(t / 36)]} ${n[Math.floor(t % 36 / 6)]} ${n[t % 6]})`;
|
|
58
|
+
}
|
|
59
|
+
let t = 8 + (e - 232) * 10;
|
|
60
|
+
return `rgb(${t} ${t} ${t})`;
|
|
61
|
+
}
|
|
62
|
+
function l(e, t, n) {
|
|
63
|
+
return s(e) && s(t) && s(n) ? `rgb(${e} ${t} ${n})` : void 0;
|
|
64
|
+
}
|
|
65
|
+
function u(e) {
|
|
66
|
+
let t = e.foreground, n = e.background;
|
|
67
|
+
if (e.inverse) {
|
|
68
|
+
let e = n ?? "var(--code-bg)";
|
|
69
|
+
n = t ?? "var(--text)", t = e;
|
|
70
|
+
}
|
|
71
|
+
let r = {};
|
|
72
|
+
t && (r.color = t), n && (r.backgroundColor = n), e.bold && (r.fontWeight = "700"), e.italic && (r.fontStyle = "italic"), e.dim && (r.opacity = .65);
|
|
73
|
+
let i = [];
|
|
74
|
+
return (e.underline || e.doubleUnderline) && i.push("underline"), e.strike && i.push("line-through"), e.overline && i.push("overline"), i.length && (r.textDecorationLine = i.join(" ")), e.doubleUnderline && (r.textDecorationStyle = "double"), e.conceal && (r.visibility = "hidden"), Object.keys(r).length ? r : void 0;
|
|
75
|
+
}
|
|
76
|
+
function d(e, t) {
|
|
77
|
+
return e === t ? !0 : !e || !t ? !1 : e.color === t.color && e.backgroundColor === t.backgroundColor && e.fontWeight === t.fontWeight && e.fontStyle === t.fontStyle && e.opacity === t.opacity && e.textDecorationLine === t.textDecorationLine && e.textDecorationStyle === t.textDecorationStyle && e.visibility === t.visibility;
|
|
78
|
+
}
|
|
79
|
+
function f(e, t) {
|
|
80
|
+
e === 0 ? Object.assign(t, a()) : e === 1 ? t.bold = !0 : e === 2 ? t.dim = !0 : e === 3 ? t.italic = !0 : e === 4 ? (t.underline = !0, t.doubleUnderline = !1) : e === 7 ? t.inverse = !0 : e === 8 ? t.conceal = !0 : e === 9 ? t.strike = !0 : e === 21 ? (t.underline = !0, t.doubleUnderline = !0) : e === 22 ? (t.bold = !1, t.dim = !1) : e === 23 ? t.italic = !1 : e === 24 ? (t.underline = !1, t.doubleUnderline = !1) : e === 27 ? t.inverse = !1 : e === 28 ? t.conceal = !1 : e === 29 ? t.strike = !1 : e >= 30 && e <= 37 ? t.foreground = i[e - 30] : e === 39 ? t.foreground = void 0 : e >= 40 && e <= 47 ? t.background = i[e - 40] : e === 49 ? t.background = void 0 : e === 53 ? t.overline = !0 : e === 55 ? t.overline = !1 : e >= 90 && e <= 97 ? t.foreground = i[e - 90 + 8] : e >= 100 && e <= 107 && (t.background = i[e - 100 + 8]);
|
|
81
|
+
}
|
|
82
|
+
function p(e, t) {
|
|
83
|
+
let n = e.split(":"), r = Number(n[0]);
|
|
84
|
+
if (r !== 38 && r !== 48) return !1;
|
|
85
|
+
let i = Number(n[1]), a;
|
|
86
|
+
if (i === 5) a = c(Number(n[n.length - 1]));
|
|
87
|
+
else if (i === 2 && n.length >= 5) {
|
|
88
|
+
let e = n.slice(-3).map(Number);
|
|
89
|
+
a = l(e[0], e[1], e[2]);
|
|
90
|
+
}
|
|
91
|
+
return a && (r === 38 ? t.foreground = a : t.background = a), !0;
|
|
92
|
+
}
|
|
93
|
+
function m(e) {
|
|
94
|
+
if (e === "") return 0;
|
|
95
|
+
if (/^\d+$/.test(e)) return Number(e);
|
|
96
|
+
}
|
|
97
|
+
function h(e, t) {
|
|
98
|
+
let n = e === "" ? ["0"] : e.split(";");
|
|
99
|
+
for (let e = 0; e < n.length; e++) {
|
|
100
|
+
let r = n[e];
|
|
101
|
+
if (r.includes(":")) {
|
|
102
|
+
p(r, t);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
let i = m(r);
|
|
106
|
+
if (i === void 0) continue;
|
|
107
|
+
if (i !== 38 && i !== 48) {
|
|
108
|
+
f(i, t);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
let a = m(n[e + 1] ?? ""), o;
|
|
112
|
+
a === 5 && e + 2 < n.length ? (o = c(m(n[e + 2])), e += 2) : a === 2 && e + 4 < n.length && (o = l(m(n[e + 2]), m(n[e + 3]), m(n[e + 4])), e += 4), o && (i === 38 ? t.foreground = o : t.background = o);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function g(e, t) {
|
|
116
|
+
for (let n = t; n < e.length; n++) {
|
|
117
|
+
let t = e.charCodeAt(n);
|
|
118
|
+
if (t >= 64 && t <= 126) return n;
|
|
119
|
+
}
|
|
120
|
+
return e.length - 1;
|
|
121
|
+
}
|
|
122
|
+
function _(t, n) {
|
|
123
|
+
for (let i = n; i < t.length; i++) {
|
|
124
|
+
if (t[i] === "\x07" || t[i] === r) return i;
|
|
125
|
+
if (t[i] === e && t[i + 1] === "\\") return i + 1;
|
|
126
|
+
}
|
|
127
|
+
return t.length - 1;
|
|
128
|
+
}
|
|
129
|
+
function v(r) {
|
|
130
|
+
let i = a(), o = [], s = [], c = "", l = "", f = !1, p = () => {
|
|
131
|
+
if (!l) return;
|
|
132
|
+
let e = u(i), t = s[s.length - 1];
|
|
133
|
+
t && d(t.style, e) ? t.text += l : s.push({
|
|
134
|
+
text: l,
|
|
135
|
+
style: e
|
|
136
|
+
}), c += l, l = "";
|
|
137
|
+
}, m = () => {
|
|
138
|
+
p(), o.push({
|
|
139
|
+
text: c,
|
|
140
|
+
segments: s
|
|
141
|
+
}), s = [], c = "";
|
|
142
|
+
}, v = 0;
|
|
143
|
+
for (; v < r.length;) {
|
|
144
|
+
let a = r[v], o = r.charCodeAt(v);
|
|
145
|
+
if (a === "\n") {
|
|
146
|
+
m(), v++;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (a === "\r") {
|
|
150
|
+
m(), v += r[v + 1] === "\n" ? 2 : 1;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
let s = a === e && r[v + 1] === "[";
|
|
154
|
+
if (s || a === t) {
|
|
155
|
+
p(), f = !0;
|
|
156
|
+
let e = v + (s ? 2 : 1), t = g(r, e);
|
|
157
|
+
t < r.length && r[t] === "m" && h(r.slice(e, t), i), v = t + 1;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
let c = a === e && (r[v + 1] === "]" || "PX^_".includes(r[v + 1] ?? ""));
|
|
161
|
+
if (c || a === n || o === 144 || o === 152 || o === 158 || o === 159) {
|
|
162
|
+
p(), f = !0, v = _(r, v + (c ? 2 : 1)) + 1;
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (a === e) {
|
|
166
|
+
for (p(), f = !0, v++; v < r.length && r.charCodeAt(v) >= 32 && r.charCodeAt(v) <= 47;) v++;
|
|
167
|
+
v < r.length && v++;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (o < 32 && a !== " " || o >= 127 && o <= 159) {
|
|
171
|
+
p(), f = !0, v++;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
l += a, v++;
|
|
175
|
+
}
|
|
176
|
+
return m(), {
|
|
177
|
+
hasAnsi: f,
|
|
178
|
+
text: o.map((e) => e.text).join("\n"),
|
|
179
|
+
lines: o
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
function y(e) {
|
|
183
|
+
return o(e) ? v(e).text : e;
|
|
184
|
+
}
|
|
185
|
+
function b(e, t = "") {
|
|
186
|
+
let n = typeof e == "string" ? {
|
|
187
|
+
text: e,
|
|
188
|
+
segments: e ? [{ text: e }] : []
|
|
189
|
+
} : e;
|
|
190
|
+
if (!t) return n.segments.map((e) => ({
|
|
191
|
+
...e,
|
|
192
|
+
marked: !1
|
|
193
|
+
}));
|
|
194
|
+
let r = n.text.toLowerCase(), i = t.toLowerCase();
|
|
195
|
+
if (!i) return n.segments.map((e) => ({
|
|
196
|
+
...e,
|
|
197
|
+
marked: !1
|
|
198
|
+
}));
|
|
199
|
+
let a = [], o = r.indexOf(i);
|
|
200
|
+
for (; o !== -1;) a.push([o, o + t.length]), o = r.indexOf(i, o + t.length);
|
|
201
|
+
if (!a.length) return n.segments.map((e) => ({
|
|
202
|
+
...e,
|
|
203
|
+
marked: !1
|
|
204
|
+
}));
|
|
205
|
+
let s = [], c = 0, l = 0;
|
|
206
|
+
for (let e of n.segments) {
|
|
207
|
+
let t = c, n = c + e.text.length, r = t;
|
|
208
|
+
for (; r < n;) {
|
|
209
|
+
for (; l < a.length && a[l][1] <= r;) l++;
|
|
210
|
+
let i = a[l], o = !!(i && i[0] < n && i[1] > r), c = o ? Math.min(n, i[1]) : Math.min(n, i?.[0] ?? n);
|
|
211
|
+
s.push({
|
|
212
|
+
text: e.text.slice(r - t, c - t),
|
|
213
|
+
style: e.style,
|
|
214
|
+
marked: o
|
|
215
|
+
}), r = c;
|
|
216
|
+
}
|
|
217
|
+
c = n;
|
|
218
|
+
}
|
|
219
|
+
return s;
|
|
220
|
+
}
|
|
221
|
+
//#endregion
|
|
222
|
+
export { y as ansiPlainText, b as ansiRenderPieces, o as hasAnsiSequences, v as parseAnsi };
|
|
223
|
+
|
|
224
|
+
//# sourceMappingURL=ansi.js.map
|