@seanmozeik/markdown-display 0.3.5 → 0.4.1
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 +41 -20
- package/dist/abap-9xbv043v.js +2 -0
- package/dist/actionscript-3-ffr5ydrs.js +2 -0
- package/dist/ada-jk2gn0pn.js +2 -0
- package/dist/andromeeda-24gktw4d.js +2 -0
- package/dist/angular-html-xg28445y.js +2 -0
- package/dist/angular-ts-1xq6mw7g.js +2 -0
- package/dist/apache-4b3b0eyy.js +2 -0
- package/dist/apex-a3awgdsm.js +2 -0
- package/dist/apl-f630k1vx.js +2 -0
- package/dist/applescript-qreypzb3.js +2 -0
- package/dist/ara-a0ghw0xy.js +2 -0
- package/dist/asciidoc-17amrzcg.js +2 -0
- package/dist/asm-1ybpdpc6.js +2 -0
- package/dist/astro-2vzy1qg5.js +2 -0
- package/dist/aurora-x-pb2h8m75.js +2 -0
- package/dist/awk-4g14sdfr.js +2 -0
- package/dist/ayu-dark-a6e49das.js +2 -0
- package/dist/ayu-light-ac787hyf.js +2 -0
- package/dist/ayu-mirage-zjtmb0bg.js +2 -0
- package/dist/ballerina-fd1yzyy8.js +2 -0
- package/dist/banner-ng06wc8g.js +12 -0
- package/dist/bat-x024z34k.js +2 -0
- package/dist/beancount-4aa1ce0k.js +2 -0
- package/dist/berry-1157ed3y.js +2 -0
- package/dist/bibtex-s0jadepe.js +2 -0
- package/dist/bicep-gx08m1nz.js +2 -0
- package/dist/bird2-3g0vanh0.js +2 -0
- package/dist/blade-22gzdntq.js +2 -0
- package/dist/bsl-r8cmv6pz.js +2 -0
- package/dist/c-g33d1744.js +2 -0
- package/dist/c3-02s6n09a.js +2 -0
- package/dist/cadence-b5x3mzqg.js +2 -0
- package/dist/cairo-4v5k49bw.js +2 -0
- package/dist/catppuccin-frappe-zze8rp4c.js +2 -0
- package/dist/catppuccin-latte-ebmyxk6e.js +2 -0
- package/dist/catppuccin-macchiato-8m9avkeq.js +2 -0
- package/dist/catppuccin-mocha-caq0gmr7.js +2 -0
- package/dist/clarity-x08cc58e.js +2 -0
- package/dist/cli-0y1zeg7v.js +3 -0
- package/dist/cli-1hj5h9f9.js +3 -0
- package/dist/cli-2h1yfjdw.js +3 -0
- package/dist/cli-30n24kzz.js +3 -0
- package/dist/cli-3fzyv189.js +3 -0
- package/dist/cli-3wvgjq8z.js +3 -0
- package/dist/cli-3z230jhh.js +3 -0
- package/dist/cli-48yac15g.js +3 -0
- package/dist/cli-58p99hkc.js +3 -0
- package/dist/cli-5jp9npt1.js +3 -0
- package/dist/cli-5v8q61ce.js +3 -0
- package/dist/cli-6jzqb50s.js +3 -0
- package/dist/cli-79y9hz07.js +3 -0
- package/dist/cli-81dmk4ts.js +3 -0
- package/dist/cli-81w52zkt.js +3 -0
- package/dist/cli-8nneg522.js +3 -0
- package/dist/cli-8qe0pqv6.js +3 -0
- package/dist/cli-a8250sbe.js +3 -0
- package/dist/cli-ac5hc1dt.js +3 -0
- package/dist/cli-app-kzae9fnq.js +411 -0
- package/dist/cli-ayvxq7qa.js +3 -0
- package/dist/cli-banpa4vr.js +7 -0
- package/dist/cli-bt5bw9b4.js +3 -0
- package/dist/cli-c0mb9wq6.js +3 -0
- package/dist/cli-dg73jpjs.js +3 -0
- package/dist/cli-e21h859y.js +3 -0
- package/dist/cli-g0wwxff6.js +3 -0
- package/dist/cli-gtjpq1mb.js +3 -0
- package/dist/cli-ha8pqpt8.js +3 -0
- package/dist/cli-hsvv3jzh.js +3 -0
- package/dist/cli-hxsnf2x8.js +3 -0
- package/dist/cli-jewjtf3e.js +3 -0
- package/dist/cli-jkvh6m6e.js +3 -0
- package/dist/cli-jqdxtesj.js +3 -0
- package/dist/cli-k2dtsfh9.js +3 -0
- package/dist/cli-m8zynty7.js +3 -0
- package/dist/cli-n5czz7gz.js +3 -0
- package/dist/cli-r2h3ee8w.js +3 -0
- package/dist/cli-rkqs0vcv.js +3 -0
- package/dist/cli-ss2qwkcc.js +3 -0
- package/dist/cli-tse5g5yz.js +3 -0
- package/dist/cli-v04mbmxe.js +3 -0
- package/dist/cli-v37wd532.js +3 -0
- package/dist/cli-vbncttvd.js +3 -0
- package/dist/cli-vt93z537.js +3 -0
- package/dist/cli-w37ddf7r.js +3 -0
- package/dist/cli-yjt0k8jp.js +3 -0
- package/dist/cli-yxfvsvk9.js +3 -0
- package/dist/clojure-vzmv55wf.js +2 -0
- package/dist/cmake-7y22bc67.js +2 -0
- package/dist/cobol-kdnhdmjz.js +2 -0
- package/dist/codeowners-x6m7bhdc.js +2 -0
- package/dist/codeql-6rz9fzqw.js +2 -0
- package/dist/coffee-hdfzr8ps.js +2 -0
- package/dist/common-lisp-zmr06g7n.js +2 -0
- package/dist/coq-r8j5c36h.js +2 -0
- package/dist/cpp-cnh8n6p9.js +2 -0
- package/dist/crystal-mrgdvpq4.js +2 -0
- package/dist/csharp-q48m96qr.js +2 -0
- package/dist/css-bd0hqx8d.js +2 -0
- package/dist/csv-4m85ky3s.js +2 -0
- package/dist/cue-tkch0b5s.js +2 -0
- package/dist/cypher-9v66whj2.js +2 -0
- package/dist/d-h7frep7v.js +2 -0
- package/dist/dark-plus-3mz49csr.js +2 -0
- package/dist/dart-0ts3fbr7.js +2 -0
- package/dist/dax-epx6f5sr.js +2 -0
- package/dist/desktop-ga56hzcd.js +2 -0
- package/dist/diff-nj0camxe.js +2 -0
- package/dist/docker-ck9z05s1.js +2 -0
- package/dist/dotenv-n24rx33c.js +2 -0
- package/dist/dracula-rt7zm8kp.js +2 -0
- package/dist/dracula-soft-ny1w766n.js +2 -0
- package/dist/dream-maker-5bdhk1xw.js +2 -0
- package/dist/edge-nes62z5q.js +2 -0
- package/dist/elixir-t2dacwg4.js +2 -0
- package/dist/elm-efkwc9kb.js +2 -0
- package/dist/emacs-lisp-aeyrqndc.js +2 -0
- package/dist/erb-1jnjam5w.js +2 -0
- package/dist/erlang-q2nxsk7t.js +2 -0
- package/dist/everforest-dark-q5afr5gg.js +2 -0
- package/dist/everforest-light-gwf12vcx.js +2 -0
- package/dist/fennel-qcrnckrq.js +2 -0
- package/dist/fish-fybdeyvp.js +2 -0
- package/dist/fluent-71z72985.js +2 -0
- package/dist/fortran-fixed-form-1gqb01bw.js +2 -0
- package/dist/fortran-free-form-kxkmegqs.js +2 -0
- package/dist/fsharp-cgvxcgsx.js +2 -0
- package/dist/gdresource-46k5gmzt.js +2 -0
- package/dist/gdscript-68kq1xvg.js +2 -0
- package/dist/gdshader-b3674jka.js +2 -0
- package/dist/genie-jyymh6js.js +2 -0
- package/dist/gherkin-7e2sjv5d.js +2 -0
- package/dist/git-commit-9wk0r5sb.js +2 -0
- package/dist/git-rebase-prhzk4j5.js +2 -0
- package/dist/github-dark-default-0vxh5df6.js +2 -0
- package/dist/github-dark-dimmed-mrkg0dkj.js +2 -0
- package/dist/github-dark-high-contrast-dfjvzvs1.js +2 -0
- package/dist/github-dark-vmmv282e.js +2 -0
- package/dist/github-light-79sz9hnx.js +2 -0
- package/dist/github-light-default-1jr038w7.js +2 -0
- package/dist/github-light-high-contrast-r2s26c8v.js +2 -0
- package/dist/gleam-0915n1m3.js +2 -0
- package/dist/glimmer-js-k7czmdfm.js +2 -0
- package/dist/glimmer-ts-194ejtg4.js +2 -0
- package/dist/glsl-dj78d5j4.js +2 -0
- package/dist/gn-1hvwh71v.js +2 -0
- package/dist/gnuplot-3hmy4dt7.js +2 -0
- package/dist/go-33wfsnyk.js +2 -0
- package/dist/graphql-0aac9yec.js +2 -0
- package/dist/groovy-ja0xcaxz.js +2 -0
- package/dist/gruvbox-dark-hard-5s80mtba.js +2 -0
- package/dist/gruvbox-dark-medium-n4khhzte.js +2 -0
- package/dist/gruvbox-dark-soft-e71n00ep.js +2 -0
- package/dist/gruvbox-light-hard-pbvye0e8.js +2 -0
- package/dist/gruvbox-light-medium-ta1n6jez.js +2 -0
- package/dist/gruvbox-light-soft-ymh9etvc.js +2 -0
- package/dist/hack-wbm9q4mm.js +2 -0
- package/dist/haml-gx26k3n5.js +2 -0
- package/dist/handlebars-x15bnv4j.js +2 -0
- package/dist/haskell-gphr2mx4.js +2 -0
- package/dist/haxe-jjqygeb7.js +2 -0
- package/dist/hcl-cr0bx0fz.js +2 -0
- package/dist/hjson-f1xxjn7x.js +2 -0
- package/dist/hlsl-dx0egzxe.js +2 -0
- package/dist/horizon-bright-4xdqr7cd.js +2 -0
- package/dist/horizon-ztsnzjdn.js +2 -0
- package/dist/houston-61g9eh8r.js +2 -0
- package/dist/html-derivative-hvxv75a7.js +2 -0
- package/dist/html-wqddzypw.js +2 -0
- package/dist/http-kh76rh1s.js +2 -0
- package/dist/hurl-4c8qps7w.js +2 -0
- package/dist/hxml-3bkn4jp8.js +2 -0
- package/dist/hy-fxpg7aaw.js +2 -0
- package/dist/imba-q8na79n7.js +2 -0
- package/dist/index-pdw64t43.js +15 -0
- package/dist/ini-s6y2n2r0.js +2 -0
- package/dist/java-bcf5924q.js +2 -0
- package/dist/javascript-qmy7d5mk.js +2 -0
- package/dist/jinja-y1162pxj.js +2 -0
- package/dist/jison-444dx63z.js +2 -0
- package/dist/json-gwwdj89t.js +2 -0
- package/dist/json5-3100ynmc.js +2 -0
- package/dist/jsonc-v2rnz0h1.js +2 -0
- package/dist/jsonl-axvqz7sn.js +2 -0
- package/dist/jsonnet-zfn0ptb5.js +2 -0
- package/dist/jssm-gej4xq4j.js +2 -0
- package/dist/jsx-zc0j90dq.js +2 -0
- package/dist/julia-as91r849.js +2 -0
- package/dist/just-zmsqbkqy.js +2 -0
- package/dist/kanagawa-dragon-d76szdr9.js +2 -0
- package/dist/kanagawa-lotus-wd9eezwr.js +2 -0
- package/dist/kanagawa-wave-acc5532t.js +2 -0
- package/dist/kdl-0nd9fhgr.js +2 -0
- package/dist/kotlin-v71xk1rn.js +2 -0
- package/dist/kusto-9mpsj4vk.js +2 -0
- package/dist/laserwave-db8954kv.js +2 -0
- package/dist/latex-xs0c2nvw.js +2 -0
- package/dist/lean-vdhz6cqv.js +2 -0
- package/dist/less-312pw0ev.js +2 -0
- package/dist/light-plus-aq99ra4y.js +2 -0
- package/dist/liquid-dmnzg3ty.js +2 -0
- package/dist/llvm-t6bzgxkv.js +2 -0
- package/dist/log-ssjyybt8.js +2 -0
- package/dist/logo-qb8mz41r.js +2 -0
- package/dist/lua-kk3ewrfz.js +2 -0
- package/dist/luau-1332gd7k.js +2 -0
- package/dist/make-ym3y5he3.js +2 -0
- package/dist/markdown-yakp348x.js +2 -0
- package/dist/marko-t2xabqrz.js +2 -0
- package/dist/material-theme-darker-vyfbvm1v.js +2 -0
- package/dist/material-theme-e94sgaj2.js +2 -0
- package/dist/material-theme-lighter-12stms50.js +2 -0
- package/dist/material-theme-ocean-vbjhpav2.js +2 -0
- package/dist/material-theme-palenight-zvsby2j7.js +2 -0
- package/dist/matlab-7z9bsz8e.js +2 -0
- package/dist/md.js +4 -0
- package/dist/mdc-fnzmnxrv.js +2 -0
- package/dist/mdx-txks2jee.js +2 -0
- package/dist/mermaid-2geg9v5h.js +2 -0
- package/dist/min-dark-p6btg8dq.js +2 -0
- package/dist/min-light-v3br1wzp.js +2 -0
- package/dist/mipsasm-npsfhz7p.js +2 -0
- package/dist/mojo-53s8q8kz.js +2 -0
- package/dist/monokai-j1kkm1ja.js +2 -0
- package/dist/moonbit-gcr903w0.js +2 -0
- package/dist/move-4vm43y7r.js +2 -0
- package/dist/narrat-4dfafp1n.js +2 -0
- package/dist/nextflow-gen0wthn.js +2 -0
- package/dist/nextflow-groovy-rntv4ct1.js +2 -0
- package/dist/nginx-md97v71f.js +2 -0
- package/dist/night-owl-bgdmn1df.js +2 -0
- package/dist/night-owl-light-jka34m0q.js +2 -0
- package/dist/nim-fngvzh2t.js +2 -0
- package/dist/nix-x4emth0n.js +2 -0
- package/dist/nord-xscwecj8.js +2 -0
- package/dist/nushell-tjxswzet.js +2 -0
- package/dist/objective-c-nk9a9yrs.js +2 -0
- package/dist/objective-cpp-p6sapemd.js +2 -0
- package/dist/ocaml-bcy7sgka.js +2 -0
- package/dist/odin-5vrv5d1s.js +2 -0
- package/dist/one-dark-pro-r96tb03e.js +2 -0
- package/dist/one-light-bfcdemmg.js +2 -0
- package/dist/openscad-w36wsg83.js +2 -0
- package/dist/pascal-zje13224.js +2 -0
- package/dist/perl-jb96y2dw.js +2 -0
- package/dist/php-66y7zdam.js +2 -0
- package/dist/pkl-j08f73a2.js +2 -0
- package/dist/plastic-d95k0yjm.js +2 -0
- package/dist/plsql-a5tqv6r5.js +2 -0
- package/dist/po-z4zztbvf.js +2 -0
- package/dist/poimandres-ft3p00vk.js +2 -0
- package/dist/polar-45ka4fdw.js +2 -0
- package/dist/postcss-ak75ftnf.js +2 -0
- package/dist/powerquery-j6z2e9xw.js +2 -0
- package/dist/powershell-ca63wk2e.js +2 -0
- package/dist/prisma-b2y4avdt.js +2 -0
- package/dist/prolog-wktkj1zv.js +2 -0
- package/dist/proto-5pd1z1zv.js +2 -0
- package/dist/pug-80k5jz6p.js +2 -0
- package/dist/puppet-66683nhq.js +2 -0
- package/dist/purescript-cmqqet79.js +2 -0
- package/dist/python-jabyzx3d.js +2 -0
- package/dist/qml-d92cbtkt.js +2 -0
- package/dist/qmldir-za85z5zc.js +2 -0
- package/dist/qss-gh7cf1zk.js +2 -0
- package/dist/r-s39xqqt9.js +2 -0
- package/dist/racket-gbjk3es0.js +2 -0
- package/dist/raku-zqwbnxmq.js +2 -0
- package/dist/razor-zg3bck80.js +2 -0
- package/dist/red-k6970d1d.js +2 -0
- package/dist/reg-gserh519.js +2 -0
- package/dist/regexp-a95pq17y.js +2 -0
- package/dist/rel-gaefrfhc.js +2 -0
- package/dist/riscv-cszphkwv.js +2 -0
- package/dist/ron-yf5x21c5.js +2 -0
- package/dist/rose-pine-1zbcjg1b.js +2 -0
- package/dist/rose-pine-dawn-tmsqdesr.js +2 -0
- package/dist/rose-pine-moon-cmwgg41b.js +2 -0
- package/dist/rosmsg-8j5zf8wv.js +2 -0
- package/dist/rst-7trgmfaq.js +2 -0
- package/dist/ruby-2svq4615.js +2 -0
- package/dist/rust-q5spk848.js +2 -0
- package/dist/sas-qhcyyrbt.js +2 -0
- package/dist/sass-qwyex0n3.js +2 -0
- package/dist/scala-jmhd870s.js +2 -0
- package/dist/scheme-5dvkrg0d.js +2 -0
- package/dist/scss-7v9m0m7d.js +2 -0
- package/dist/sdbl-93kewz9d.js +2 -0
- package/dist/shaderlab-97e0480d.js +2 -0
- package/dist/shellscript-6mxk36va.js +2 -0
- package/dist/shellsession-c7mva2xt.js +2 -0
- package/dist/slack-dark-y62znhvd.js +2 -0
- package/dist/slack-ochin-esm65n7t.js +2 -0
- package/dist/smalltalk-fkw0jz97.js +2 -0
- package/dist/snazzy-light-gdz5rv1k.js +2 -0
- package/dist/solarized-dark-zgcy8wm4.js +2 -0
- package/dist/solarized-light-66pg8xzk.js +2 -0
- package/dist/solidity-q5d4ht8n.js +2 -0
- package/dist/soy-hhhx5cj0.js +2 -0
- package/dist/sparql-hm2pqpge.js +2 -0
- package/dist/splunk-w5h843nw.js +2 -0
- package/dist/sql-pas3x5ke.js +2 -0
- package/dist/ssh-config-n2kt2cym.js +2 -0
- package/dist/stata-9j4p5d8c.js +2 -0
- package/dist/stylus-9s01c3kp.js +2 -0
- package/dist/surrealql-gxntasn8.js +2 -0
- package/dist/svelte-nvgnrg0t.js +2 -0
- package/dist/swift-j07yna4k.js +2 -0
- package/dist/synthwave-84-d7yw2bf0.js +2 -0
- package/dist/system-verilog-30bs377q.js +2 -0
- package/dist/systemd-4252hbv8.js +2 -0
- package/dist/talonscript-qbxwesm0.js +2 -0
- package/dist/tasl-m18yj07w.js +2 -0
- package/dist/tcl-qfvbg90f.js +2 -0
- package/dist/templ-msv3jzfx.js +2 -0
- package/dist/terraform-w6q6kyy0.js +2 -0
- package/dist/tex-1h8385qh.js +2 -0
- package/dist/tokyo-night-11kv01fx.js +2 -0
- package/dist/toml-02rkjp4b.js +2 -0
- package/dist/ts-tags-zmwja4z5.js +2 -0
- package/dist/tsv-t7svepav.js +2 -0
- package/dist/tsx-5ck84m8p.js +2 -0
- package/dist/turtle-synzazfp.js +2 -0
- package/dist/twig-41166c5j.js +2 -0
- package/dist/typescript-cqey3bjn.js +2 -0
- package/dist/typespec-ddd8ynv9.js +2 -0
- package/dist/typst-ex558216.js +2 -0
- package/dist/v-1bn5h0en.js +2 -0
- package/dist/vala-078a3s0x.js +2 -0
- package/dist/vb-5sq26hr3.js +2 -0
- package/dist/verilog-ekh48e1e.js +2 -0
- package/dist/vesper-j18bqpbg.js +2 -0
- package/dist/vhdl-fhjza3xz.js +2 -0
- package/dist/viml-9jfzgmge.js +2 -0
- package/dist/vitesse-black-3b8qwvte.js +2 -0
- package/dist/vitesse-dark-bsvtvg1h.js +2 -0
- package/dist/vitesse-light-egp8j39z.js +2 -0
- package/dist/vue-6yd2mg34.js +2 -0
- package/dist/vue-html-y5mtwexa.js +2 -0
- package/dist/vue-vine-732a4732.js +2 -0
- package/dist/vyper-h4fr87vt.js +2 -0
- package/dist/wasm-c0hbsbpk.js +2 -0
- package/dist/wasm-xe5dxyky.js +2 -0
- package/dist/wenyan-pf79c6ns.js +2 -0
- package/dist/wgsl-20v05k5y.js +2 -0
- package/dist/wikitext-6vf691aw.js +2 -0
- package/dist/wit-gp0gbqxw.js +2 -0
- package/dist/wolfram-y1r4y7zp.js +2 -0
- package/dist/xml-9fqvyqnc.js +2 -0
- package/dist/xsl-xpzpqrmc.js +2 -0
- package/dist/yaml-885sffq4.js +2 -0
- package/dist/zenscript-0k3ar154.js +2 -0
- package/dist/zig-h5gys7p5.js +2 -0
- package/package.json +51 -23
- package/src/app/cli-info.ts +90 -0
- package/src/app/errors.ts +19 -0
- package/src/app/file-not-found-error.ts +6 -0
- package/src/app/format-error.ts +35 -0
- package/src/app/invalid-theme-error.ts +6 -0
- package/src/app/pager-error.ts +5 -0
- package/src/app/run-md.ts +197 -0
- package/src/app/stdin-read-error.ts +5 -0
- package/src/cli/options.ts +20 -0
- package/src/cli-app.ts +113 -0
- package/src/cli.ts +64 -0
- package/src/config/errors.ts +6 -0
- package/src/config/index.ts +183 -0
- package/src/config/read-error.ts +6 -0
- package/src/constants.ts +2 -0
- package/src/index.ts +51 -0
- package/src/lib/ansi.ts +24 -0
- package/src/lib/config.ts +2 -0
- package/src/lib/default-config.toml +25 -0
- package/src/lib/elements/blockquote.ts +34 -0
- package/src/lib/elements/code.ts +326 -0
- package/src/lib/elements/heading.ts +39 -0
- package/src/lib/elements/link.ts +33 -0
- package/src/lib/elements/list.ts +89 -0
- package/src/lib/elements/table.ts +65 -0
- package/src/lib/elements/text.ts +237 -0
- package/src/lib/languages.ts +123 -0
- package/src/lib/layout.ts +67 -0
- package/src/lib/pager.ts +121 -0
- package/src/lib/parser.ts +299 -0
- package/src/lib/render-code-blocks.ts +25 -0
- package/src/lib/render.ts +37 -0
- package/src/lib/shiki.ts +197 -0
- package/src/lib/width.ts +21 -0
- package/src/paths.ts +14 -0
- package/src/types/hyphen.d.ts +3 -0
- package/src/ui/banner.ts +23 -0
- package/src/ui/picker.ts +120 -0
- package/src/ui/themes/ansi.ts +161 -0
- package/src/ui/themes/color-support.ts +106 -0
- package/src/ui/themes/generated.ts +748 -0
- package/src/ui/themes/index.ts +58 -0
- package/src/ui/themes/overrides.ts +83 -0
- package/src/ui/themes/semantic.ts +125 -0
- package/src/ui/themes/types.ts +67 -0
- package/dist/index.js +0 -194
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { hyphenateSync } from 'hyphen/en';
|
|
2
|
+
|
|
3
|
+
// Src/lib/elements/text.ts
|
|
4
|
+
import { getTextColor } from '../../ui/themes/semantic';
|
|
5
|
+
import { visibleLength } from '../ansi';
|
|
6
|
+
|
|
7
|
+
interface TextConfig {
|
|
8
|
+
width: number;
|
|
9
|
+
hyphenation: boolean;
|
|
10
|
+
locale?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface WrapOptions {
|
|
14
|
+
hyphenation: boolean;
|
|
15
|
+
locale: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const SOFT_HYPHEN = '\u00AD';
|
|
19
|
+
const SOFT_HYPHEN_REGEX = /\u00AD/gu;
|
|
20
|
+
|
|
21
|
+
// ANSI escape character - used to build regex dynamically to satisfy linter
|
|
22
|
+
const ESC = '\u001B';
|
|
23
|
+
|
|
24
|
+
// Matches ANSI-styled content: \u001B[...m (content) \u001B[0m
|
|
25
|
+
const ANSI_STYLED_BLOCK = new RegExp(`${ESC}\\[[0-9;]*m[^${ESC}]*${ESC}\\[0m`, 'gu');
|
|
26
|
+
|
|
27
|
+
// Marker for preserving ANSI blocks during splitting (uses NUL which won't appear in text)
|
|
28
|
+
const NUL = '\u0000';
|
|
29
|
+
const MARKER_REGEX = new RegExp(`${NUL}ANSI(\\d+)${NUL}`, 'gu');
|
|
30
|
+
|
|
31
|
+
const NARROW_WRAP_WIDTH = 50;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Split text into words while keeping ANSI-styled content as atomic units.
|
|
35
|
+
* This prevents inline code backgrounds from bleeding when text wraps.
|
|
36
|
+
*/
|
|
37
|
+
const splitPreservingAnsi = (text: string): string[] => {
|
|
38
|
+
const styledBlocks: string[] = [];
|
|
39
|
+
const markedText = text.replace(ANSI_STYLED_BLOCK, (match) => {
|
|
40
|
+
styledBlocks.push(match);
|
|
41
|
+
return `${NUL}ANSI${styledBlocks.length - 1}${NUL}`;
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const parts = markedText.split(/\s+/u);
|
|
45
|
+
|
|
46
|
+
return parts.map((part) =>
|
|
47
|
+
part.replace(MARKER_REGEX, (_, idx) => styledBlocks[Number(idx)] ?? ''),
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const stripSoftHyphens = (text: string): string => {
|
|
52
|
+
return text.replace(SOFT_HYPHEN_REGEX, '');
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const hyphenateText = (text: string): string => {
|
|
56
|
+
return hyphenateSync(text);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Hyphenate text while preserving ANSI-styled blocks (like inline code).
|
|
61
|
+
* Only plain text segments get hyphenated; styled blocks pass through unchanged.
|
|
62
|
+
*/
|
|
63
|
+
const hyphenatePreservingAnsi = (text: string): string => {
|
|
64
|
+
// Split into segments: [plain, styled, plain, styled, ...]
|
|
65
|
+
const segments: string[] = [];
|
|
66
|
+
let lastIndex = 0;
|
|
67
|
+
|
|
68
|
+
// Reset regex state
|
|
69
|
+
ANSI_STYLED_BLOCK.lastIndex = 0;
|
|
70
|
+
|
|
71
|
+
for (
|
|
72
|
+
let match = ANSI_STYLED_BLOCK.exec(text);
|
|
73
|
+
match !== null;
|
|
74
|
+
match = ANSI_STYLED_BLOCK.exec(text)
|
|
75
|
+
) {
|
|
76
|
+
// Plain text before this styled block
|
|
77
|
+
if (match.index > lastIndex) {
|
|
78
|
+
segments.push(hyphenateText(text.slice(lastIndex, match.index)));
|
|
79
|
+
}
|
|
80
|
+
// The styled block itself (unchanged)
|
|
81
|
+
segments.push(match[0]);
|
|
82
|
+
lastIndex = match.index + match[0].length;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Remaining plain text after last styled block
|
|
86
|
+
if (lastIndex < text.length) {
|
|
87
|
+
segments.push(hyphenateText(text.slice(lastIndex)));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return segments.join('');
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const findSoftHyphenPositions = (text: string): number[] => {
|
|
94
|
+
const positions: number[] = [];
|
|
95
|
+
let idx = text.indexOf(SOFT_HYPHEN);
|
|
96
|
+
while (idx !== -1) {
|
|
97
|
+
positions.push(idx);
|
|
98
|
+
idx = text.indexOf(SOFT_HYPHEN, idx + 1);
|
|
99
|
+
}
|
|
100
|
+
return positions;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Find the best soft hyphen break point that fits within maxWidth.
|
|
105
|
+
* Returns the position, or -1 if no suitable break exists.
|
|
106
|
+
*/
|
|
107
|
+
const findBestBreakPoint = (text: string, maxWidth: number): number => {
|
|
108
|
+
const positions = findSoftHyphenPositions(text);
|
|
109
|
+
let best = -1;
|
|
110
|
+
for (const pos of positions) {
|
|
111
|
+
const beforeBreak = text.slice(0, pos);
|
|
112
|
+
// +1 for the visible hyphen we'll add
|
|
113
|
+
if (visibleLength(stripSoftHyphens(beforeBreak)) + 1 <= maxWidth) {
|
|
114
|
+
best = pos;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return best;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Try to split a word at a syllable boundary to fill remaining line space.
|
|
122
|
+
* Returns [partThatFits, remainder] if a good break point exists, or null if not.
|
|
123
|
+
*/
|
|
124
|
+
const trySplitWordToFill = (
|
|
125
|
+
word: string,
|
|
126
|
+
remainingSpace: number,
|
|
127
|
+
width: number,
|
|
128
|
+
): [string, string] | null => {
|
|
129
|
+
// More aggressive hyphenation for narrow widths
|
|
130
|
+
// Narrow (< 50): fill even with 2 chars
|
|
131
|
+
// Wide (>= 50): need at least 3 chars to be worth it
|
|
132
|
+
const minFragment = width < NARROW_WRAP_WIDTH ? 2 : 3;
|
|
133
|
+
if (remainingSpace < minFragment) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const breakAt = findBestBreakPoint(word, remainingSpace);
|
|
138
|
+
if (breakAt <= 0) {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return [`${stripSoftHyphens(word.slice(0, breakAt))}-`, word.slice(breakAt + 1)];
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const breakWord = (word: string, width: number): string => {
|
|
146
|
+
const lines: string[] = [];
|
|
147
|
+
let remaining = word;
|
|
148
|
+
|
|
149
|
+
while (visibleLength(remaining) > width) {
|
|
150
|
+
const breakAt = findBestBreakPoint(remaining, width);
|
|
151
|
+
|
|
152
|
+
if (breakAt > 0) {
|
|
153
|
+
lines.push(`${stripSoftHyphens(remaining.slice(0, breakAt))}-`);
|
|
154
|
+
remaining = remaining.slice(breakAt + 1);
|
|
155
|
+
} else {
|
|
156
|
+
lines.push(stripSoftHyphens(remaining.slice(0, width)));
|
|
157
|
+
remaining = remaining.slice(width);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (remaining) {
|
|
162
|
+
lines.push(stripSoftHyphens(remaining));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return lines.join('\n');
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const wrapText = (text: string, width: number, options?: WrapOptions): string => {
|
|
169
|
+
const shouldHyphenate = options?.hyphenation ?? false;
|
|
170
|
+
|
|
171
|
+
const wrapSingleLine = (line: string): string => {
|
|
172
|
+
// Hyphenate only non-ANSI text to protect inline code from being split
|
|
173
|
+
const processedLine = shouldHyphenate ? hyphenatePreservingAnsi(line) : line;
|
|
174
|
+
|
|
175
|
+
// Use ANSI-aware split to keep styled content (like inline code) atomic
|
|
176
|
+
const words = splitPreservingAnsi(processedLine);
|
|
177
|
+
const lines: string[] = [];
|
|
178
|
+
let currentLine = '';
|
|
179
|
+
|
|
180
|
+
for (const word of words) {
|
|
181
|
+
const testLine = currentLine ? `${currentLine} ${word}` : word;
|
|
182
|
+
|
|
183
|
+
if (visibleLength(testLine) <= width) {
|
|
184
|
+
// Word fits - strip soft hyphens since we don't need to break here
|
|
185
|
+
const cleanWord = stripSoftHyphens(word);
|
|
186
|
+
currentLine = currentLine ? `${currentLine} ${cleanWord}` : cleanWord;
|
|
187
|
+
} else if (currentLine) {
|
|
188
|
+
// Word doesn't fit - try to split it to fill current line
|
|
189
|
+
// Reserve one column for the space before the wrapped word fragment
|
|
190
|
+
const remainingSpace = width - visibleLength(currentLine) - 1;
|
|
191
|
+
const split = shouldHyphenate ? trySplitWordToFill(word, remainingSpace, width) : null;
|
|
192
|
+
|
|
193
|
+
if (split) {
|
|
194
|
+
// Fill current line with first part of word
|
|
195
|
+
const [firstPart, remainder] = split;
|
|
196
|
+
lines.push(`${stripSoftHyphens(currentLine)} ${firstPart}`);
|
|
197
|
+
// Continue with remainder (may need further breaking)
|
|
198
|
+
currentLine = breakWord(remainder, width);
|
|
199
|
+
} else {
|
|
200
|
+
// Can't split usefully - push current line and start fresh
|
|
201
|
+
lines.push(stripSoftHyphens(currentLine));
|
|
202
|
+
currentLine = breakWord(word, width);
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
205
|
+
// Word alone is too long - break it
|
|
206
|
+
const broken = breakWord(word, width);
|
|
207
|
+
const brokenLines = broken.split('\n');
|
|
208
|
+
lines.push(...brokenLines.slice(0, -1));
|
|
209
|
+
currentLine = brokenLines.at(-1) ?? '';
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (currentLine) {
|
|
214
|
+
// Strip any remaining soft hyphens from the last line
|
|
215
|
+
lines.push(stripSoftHyphens(currentLine));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return lines.join('\n');
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// Preserve explicit line breaks (e.g. Markdown <br> / hard breaks)
|
|
222
|
+
return text
|
|
223
|
+
.split('\n')
|
|
224
|
+
.map((line) => wrapSingleLine(line))
|
|
225
|
+
.join('\n');
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const renderText = (text: string, config: TextConfig): string => {
|
|
229
|
+
const wrapped = wrapText(text, config.width, {
|
|
230
|
+
hyphenation: config.hyphenation,
|
|
231
|
+
locale: config.locale ?? 'en-us',
|
|
232
|
+
});
|
|
233
|
+
const textColor = getTextColor();
|
|
234
|
+
return `${textColor(wrapped)}\n`;
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
export { renderText, wrapText };
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// Src/lib/languages.ts
|
|
2
|
+
|
|
3
|
+
interface LanguageInfo {
|
|
4
|
+
icon: string;
|
|
5
|
+
label: string;
|
|
6
|
+
color: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Nerd Font icons for programming languages (from nvim-web-devicons)
|
|
10
|
+
// Reference: icons.lua - replace __ICON_X__ placeholders with icons from that file
|
|
11
|
+
const LANGUAGES: Record<string, LanguageInfo> = {
|
|
12
|
+
bash: { color: '#89E051', icon: '', label: 'bash' },
|
|
13
|
+
c: { color: '#599EFF', icon: '', label: 'c' },
|
|
14
|
+
clojure: { color: '#8DC149', icon: '', label: 'clj' },
|
|
15
|
+
cmake: { color: '#DCE3EB', icon: '', label: 'cmake' },
|
|
16
|
+
cpp: { color: '#519ABA', icon: '', label: 'cpp' },
|
|
17
|
+
csharp: { color: '#596706', icon: '', label: 'cs' },
|
|
18
|
+
css: { color: '#663399', icon: '', label: 'css' },
|
|
19
|
+
csv: { color: '#89E051', icon: '', label: 'csv' },
|
|
20
|
+
diff: { color: '#41535B', icon: '', label: 'diff' },
|
|
21
|
+
dockerfile: { color: '#458EE6', icon: '', label: 'docker' },
|
|
22
|
+
elixir: { color: '#A074C4', icon: '', label: 'ex' },
|
|
23
|
+
erlang: { color: '#B83998', icon: '', label: 'erl' },
|
|
24
|
+
go: { color: '#00ADD8', icon: '', label: 'go' },
|
|
25
|
+
graphql: { color: '#E535AB', icon: '', label: 'gql' },
|
|
26
|
+
haskell: { color: '#A074C4', icon: '', label: 'hs' },
|
|
27
|
+
html: { color: '#E44D26', icon: '', label: 'html' },
|
|
28
|
+
java: { color: '#CC3E44', icon: '', label: 'java' },
|
|
29
|
+
javascript: { color: '#CBCB41', icon: '', label: 'js' },
|
|
30
|
+
json: { color: '#CBCB41', icon: '', label: 'json' },
|
|
31
|
+
julia: { color: '#A270BA', icon: '', label: 'jl' },
|
|
32
|
+
kotlin: { color: '#7F52FF', icon: '', label: 'kt' },
|
|
33
|
+
lua: { color: '#51A0CF', icon: '', label: 'lua' },
|
|
34
|
+
makefile: { color: '#6D8086', icon: '', label: 'make' },
|
|
35
|
+
markdown: { color: '#DDDDDD', icon: '', label: 'md' },
|
|
36
|
+
nim: { color: '#F3D400', icon: '', label: 'nim' },
|
|
37
|
+
ocaml: { color: '#E37933', icon: '', label: 'ml' },
|
|
38
|
+
perl: { color: '#519ABA', icon: '', label: 'pl' },
|
|
39
|
+
php: { color: '#A074C4', icon: '', label: 'php' },
|
|
40
|
+
prisma: { color: '#5A67D8', icon: '', label: 'prisma' },
|
|
41
|
+
python: { color: '#FFBC03', icon: '', label: 'py' },
|
|
42
|
+
r: { color: '#2266BA', icon: '', label: 'r' },
|
|
43
|
+
ruby: { color: '#701516', icon: '', label: 'rb' },
|
|
44
|
+
rust: { color: '#DEA584', icon: '', label: 'rs' },
|
|
45
|
+
scala: { color: '#CC3E44', icon: '', label: 'scala' },
|
|
46
|
+
scss: { color: '#F55385', icon: '', label: 'scss' },
|
|
47
|
+
shell: { color: '#4D5A5E', icon: '', label: 'sh' },
|
|
48
|
+
sql: { color: '#DAD8D8', icon: '', label: 'sql' },
|
|
49
|
+
svelte: { color: '#FF3E00', icon: '', label: 'svelte' },
|
|
50
|
+
swift: { color: '#E37933', icon: '', label: 'swift' },
|
|
51
|
+
tex: { color: '#3D6117', icon: '', label: 'tex' },
|
|
52
|
+
toml: { color: '#9C4221', icon: '', label: 'toml' },
|
|
53
|
+
typescript: { color: '#519ABA', icon: '', label: 'ts' },
|
|
54
|
+
vim: { color: '#019833', icon: '', label: 'vim' },
|
|
55
|
+
vue: { color: '#8DC149', icon: '', label: 'vue' },
|
|
56
|
+
xml: { color: '#E37933', icon: '', label: 'xml' },
|
|
57
|
+
yaml: { color: '#6D8086', icon: '', label: 'yaml' },
|
|
58
|
+
zig: { color: '#F69A1B', icon: '', label: 'zig' },
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// Aliases for common short names and variants
|
|
62
|
+
const LANG_ALIASES: Record<string, string> = {
|
|
63
|
+
cc: 'cpp',
|
|
64
|
+
cjs: 'javascript',
|
|
65
|
+
cs: 'csharp',
|
|
66
|
+
cxx: 'cpp',
|
|
67
|
+
erl: 'erlang',
|
|
68
|
+
ex: 'elixir',
|
|
69
|
+
exs: 'elixir',
|
|
70
|
+
h: 'c',
|
|
71
|
+
hpp: 'cpp',
|
|
72
|
+
hs: 'haskell',
|
|
73
|
+
jl: 'julia',
|
|
74
|
+
js: 'javascript',
|
|
75
|
+
kt: 'kotlin',
|
|
76
|
+
md: 'markdown',
|
|
77
|
+
mjs: 'javascript',
|
|
78
|
+
ml: 'ocaml',
|
|
79
|
+
pl: 'perl',
|
|
80
|
+
py: 'python',
|
|
81
|
+
rb: 'ruby',
|
|
82
|
+
rs: 'rust',
|
|
83
|
+
sh: 'bash',
|
|
84
|
+
shell: 'bash',
|
|
85
|
+
ts: 'typescript',
|
|
86
|
+
yml: 'yaml',
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const normalizeLang = (lang: string): string => {
|
|
90
|
+
const lower = lang.toLowerCase();
|
|
91
|
+
return LANG_ALIASES[lower] ?? lower;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const getLanguageLabel = (lang: string, nerdFontsEnabled: boolean): string => {
|
|
95
|
+
const normalized = normalizeLang(lang);
|
|
96
|
+
const info = LANGUAGES[normalized];
|
|
97
|
+
if (nerdFontsEnabled && info !== undefined && info.icon.length > 0) {
|
|
98
|
+
return info.icon;
|
|
99
|
+
}
|
|
100
|
+
return normalized;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const resolveNerdFonts = (configValue: 'auto' | boolean): boolean =>
|
|
104
|
+
configValue === 'auto' ? false : configValue;
|
|
105
|
+
|
|
106
|
+
// Terminals known to typically have nerd fonts installed
|
|
107
|
+
const NERD_FONT_TERMINALS = ['WezTerm', 'kitty', 'Alacritty'];
|
|
108
|
+
|
|
109
|
+
const supportsNerdFonts = (): boolean => {
|
|
110
|
+
// Explicit override via env var
|
|
111
|
+
if (Bun.env['NERD_FONTS'] === '1' || Bun.env['NERD_FONTS'] === 'true') {
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
if (Bun.env['NERD_FONTS'] === '0' || Bun.env['NERD_FONTS'] === 'false') {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Auto-detect based on terminal
|
|
119
|
+
const term = Bun.env['TERM_PROGRAM'] ?? '';
|
|
120
|
+
return NERD_FONT_TERMINALS.some((t) => term.includes(t));
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export { getLanguageLabel, LANGUAGES, normalizeLang, resolveNerdFonts, supportsNerdFonts };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Src/lib/layout.ts
|
|
2
|
+
|
|
3
|
+
interface LayoutConfig {
|
|
4
|
+
/** 0 = disabled, 80-120 = constrain and center */
|
|
5
|
+
maxWidth: number;
|
|
6
|
+
/** Enable responsive padding */
|
|
7
|
+
padding: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface LayoutResult {
|
|
11
|
+
contentWidth: number;
|
|
12
|
+
sidePadding: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const TERMINAL_EDGE_PADDING = 2;
|
|
16
|
+
const NARROW_CONTENT_THRESHOLD = 60;
|
|
17
|
+
const MEDIUM_CONTENT_THRESHOLD = 100;
|
|
18
|
+
const NARROW_SIDE_PADDING = 1;
|
|
19
|
+
const MEDIUM_SIDE_PADDING = 2;
|
|
20
|
+
const WIDE_SIDE_PADDING = 3;
|
|
21
|
+
|
|
22
|
+
const sidePaddingForWidth = (contentWidth: number): number => {
|
|
23
|
+
if (contentWidth < NARROW_CONTENT_THRESHOLD) {
|
|
24
|
+
return NARROW_SIDE_PADDING;
|
|
25
|
+
}
|
|
26
|
+
if (contentWidth <= MEDIUM_CONTENT_THRESHOLD) {
|
|
27
|
+
return MEDIUM_SIDE_PADDING;
|
|
28
|
+
}
|
|
29
|
+
return WIDE_SIDE_PADDING;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const calculateLayout = (
|
|
33
|
+
terminalWidth: number,
|
|
34
|
+
defaultContentWidth: number,
|
|
35
|
+
config: LayoutConfig,
|
|
36
|
+
): LayoutResult => {
|
|
37
|
+
const { maxWidth, padding } = config;
|
|
38
|
+
|
|
39
|
+
if (maxWidth > 0) {
|
|
40
|
+
const contentWidth = Math.min(maxWidth, terminalWidth - TERMINAL_EDGE_PADDING);
|
|
41
|
+
const sidePadding = Math.floor((terminalWidth - contentWidth) / 2);
|
|
42
|
+
return { contentWidth, sidePadding };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!padding) {
|
|
46
|
+
return { contentWidth: defaultContentWidth, sidePadding: 0 };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const sidePadding = sidePaddingForWidth(defaultContentWidth);
|
|
50
|
+
|
|
51
|
+
return { contentWidth: defaultContentWidth - sidePadding * 2, sidePadding };
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const applyPadding = (content: string, sidePadding: number): string => {
|
|
55
|
+
if (sidePadding === 0) {
|
|
56
|
+
return content;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const paddingStr = ' '.repeat(sidePadding);
|
|
60
|
+
return content
|
|
61
|
+
.split('\n')
|
|
62
|
+
.map((line) => `${paddingStr}${line}`)
|
|
63
|
+
.join('\n');
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export type { LayoutConfig, LayoutResult };
|
|
67
|
+
export { applyPadding, calculateLayout };
|
package/src/lib/pager.ts
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// Src/lib/pager.ts
|
|
2
|
+
import { Effect, Schema } from 'effect';
|
|
3
|
+
|
|
4
|
+
import { stripAnsi } from './ansi';
|
|
5
|
+
|
|
6
|
+
class PagerPipeError extends Schema.TaggedErrorClass<PagerPipeError>()('PagerPipeError', {
|
|
7
|
+
cause: Schema.Unknown,
|
|
8
|
+
}) {}
|
|
9
|
+
|
|
10
|
+
enum PagingMode {
|
|
11
|
+
Always = 'always',
|
|
12
|
+
QuitIfOneScreen = 'quit-if-one-screen',
|
|
13
|
+
Never = 'never',
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface PagerConfig {
|
|
17
|
+
command: string;
|
|
18
|
+
args: string[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface PagingContext {
|
|
22
|
+
stdoutTTY: boolean;
|
|
23
|
+
stdinTTY: boolean;
|
|
24
|
+
lines: number;
|
|
25
|
+
height: number;
|
|
26
|
+
noPager?: boolean;
|
|
27
|
+
forceAlways?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const shouldUsePager = (ctx: PagingContext): PagingMode => {
|
|
31
|
+
if (ctx.noPager === true || !ctx.stdoutTTY) {
|
|
32
|
+
return PagingMode.Never;
|
|
33
|
+
}
|
|
34
|
+
if (ctx.forceAlways === true) {
|
|
35
|
+
return PagingMode.Always;
|
|
36
|
+
}
|
|
37
|
+
if (ctx.lines <= ctx.height) {
|
|
38
|
+
return PagingMode.Never;
|
|
39
|
+
}
|
|
40
|
+
return PagingMode.QuitIfOneScreen;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const getPagerCommand = (
|
|
44
|
+
config: PagerConfig,
|
|
45
|
+
): { command: string; args: string[]; env: Record<string, string> } => {
|
|
46
|
+
// Priority: config > MD_PAGER > PAGER > less (bat pattern)
|
|
47
|
+
const command = config.command || (Bun.env['MD_PAGER'] ?? Bun.env['PAGER'] ?? 'less');
|
|
48
|
+
|
|
49
|
+
// For less, inject smart defaults if no args configured
|
|
50
|
+
let { args } = config;
|
|
51
|
+
if (command === 'less' && args.length === 0) {
|
|
52
|
+
// Raw control chars (nerd fonts), quit-if-one-screen, quit-on-interrupt, no-init
|
|
53
|
+
args = ['-r', '-F', '-K', '-X'];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
args,
|
|
58
|
+
command,
|
|
59
|
+
env: {
|
|
60
|
+
LESSCHARSET: 'utf8',
|
|
61
|
+
// Display Unicode PUA chars (nerd fonts) as-is, not escaped
|
|
62
|
+
LESSUTFBINFMT: '*d',
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const countLines = (content: string, width?: number): number => {
|
|
68
|
+
if (content.length === 0) {
|
|
69
|
+
return 1;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const lines = content.split('\n');
|
|
73
|
+
if (width === undefined || width === 0) {
|
|
74
|
+
return lines.length;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let total = 0;
|
|
78
|
+
for (const line of lines) {
|
|
79
|
+
const visibleLength = stripAnsi(line).length;
|
|
80
|
+
total += Math.max(1, Math.ceil(visibleLength / width));
|
|
81
|
+
}
|
|
82
|
+
return total;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export const pipeToLess = Effect.fn('md.pipe-to-less')(function* pipeToLessGen(
|
|
86
|
+
content: string,
|
|
87
|
+
config: PagerConfig,
|
|
88
|
+
) {
|
|
89
|
+
const { command, args, env } = getPagerCommand(config);
|
|
90
|
+
|
|
91
|
+
const proc = Bun.spawn([command, ...args], {
|
|
92
|
+
env: { ...process.env, ...env },
|
|
93
|
+
stderr: 'inherit',
|
|
94
|
+
stdin: 'pipe',
|
|
95
|
+
stdout: 'inherit',
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
yield* Effect.tryPromise({
|
|
99
|
+
catch: (cause) => new PagerPipeError({ cause }),
|
|
100
|
+
try: () => Promise.resolve(proc.stdin.write(content)),
|
|
101
|
+
});
|
|
102
|
+
yield* Effect.tryPromise({
|
|
103
|
+
catch: (cause) => new PagerPipeError({ cause }),
|
|
104
|
+
try: () => Promise.resolve(proc.stdin.end()),
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
yield* Effect.tryPromise({
|
|
108
|
+
catch: (cause) => new PagerPipeError({ cause }),
|
|
109
|
+
try: () => proc.exited,
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
export const shouldUseColor = (): boolean => {
|
|
114
|
+
// Respect NO_COLOR standard (bat pattern)
|
|
115
|
+
if (Bun.env['NO_COLOR'] !== undefined) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
return process.stdout.isTTY;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export { PagerPipeError, PagingMode };
|