@rip-lang/print 0.1.3 → 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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/print.rip +77 -15
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rip-lang/print",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Syntax-highlighted source code printer — Shiki-powered, serves once, auto-opens browser",
5
5
  "type": "module",
6
6
  "main": "print.rip",
package/print.rip CHANGED
@@ -200,11 +200,10 @@ def highlightCode(code, lang)
200
200
  # fall through
201
201
  highlighted ?= escapeHtml code
202
202
 
203
- # Add line numbers
203
+ # Add line numbers (fixed 4-digit width for consistent gutter)
204
204
  lines = highlighted.split('\n')
205
- width = String(lines.length).length
206
205
  numbered = lines.map (line, i) ->
207
- num = String(i + 1).padStart(width)
206
+ num = String(i + 1).padStart(4)
208
207
  "<span class=\"line-num\">#{num}</span> #{line}"
209
208
  numbered.join('\n')
210
209
 
@@ -274,11 +273,40 @@ for section, i in sections
274
273
 
275
274
  """
276
275
 
277
- # Read highlight.js CSS theme
278
- hljsTheme = if dark then 'github-dark' else 'github'
276
+ # Load highlight.js theme CSS files
279
277
  hljsMain = import.meta.resolve 'highlight.js'
280
278
  hljsDir = join hljsMain.replace('file://', '').replace(/\/[^/]+$/, ''), '..', 'styles'
281
- hljsCss = readFileSync join(hljsDir, "#{hljsTheme}.css"), 'utf-8'
279
+
280
+ themes = [
281
+ { id: 'github', name: 'GitHub Light', dark: false }
282
+ { id: 'atom-one-light', name: 'Atom One Light', dark: false }
283
+ { id: 'vs', name: 'Visual Studio', dark: false }
284
+ { id: 'xcode', name: 'Xcode', dark: false }
285
+ { id: 'intellij-light', name: 'IntelliJ Light', dark: false }
286
+ { id: 'stackoverflow-light', name: 'Stack Overflow', dark: false }
287
+ { id: 'default', name: 'Default Light', dark: false }
288
+ { id: 'github-dark', name: 'GitHub Dark', dark: true }
289
+ { id: 'atom-one-dark', name: 'Atom One Dark', dark: true }
290
+ { id: 'monokai', name: 'Monokai', dark: true }
291
+ { id: 'nord', name: 'Nord', dark: true }
292
+ { id: 'vs2015', name: 'VS 2015 Dark', dark: true }
293
+ { id: 'tokyo-night-dark', name: 'Tokyo Night', dark: true }
294
+ { id: 'night-owl', name: 'Night Owl', dark: true }
295
+ ]
296
+
297
+ defaultTheme = if dark then 'github-dark' else 'github'
298
+
299
+ # Load all theme CSS into a JS object for runtime switching
300
+ defaultCss = readFileSync join(hljsDir, "#{defaultTheme}.css"), 'utf-8'
301
+ themeEntries = []
302
+ themeOptions = ''
303
+ for theme in themes
304
+ css = readFileSync join(hljsDir, "#{theme.id}.css"), 'utf-8'
305
+ encoded = Buffer.from(css).toString('base64')
306
+ themeEntries.push "'#{theme.id}':{d:#{theme.dark},c:atob('#{encoded}')}"
307
+ selected = if theme.id is defaultTheme then ' selected' else ''
308
+ themeOptions += "<option value=\"#{theme.id}\" data-dark=\"#{theme.dark}\"#{selected}>#{theme.name}</option>\n"
309
+ themeData = themeEntries.join(",\n ")
282
310
 
283
311
  html = """
284
312
  <!DOCTYPE html>
@@ -287,11 +315,14 @@ html = """
287
315
  <meta charset="utf-8">
288
316
  <title>rip-print</title>
289
317
  <link rel="icon" href="data:,">
290
- <style>#{hljsCss}</style>
318
+ <style id="hljs-theme">#{defaultCss}</style>
291
319
  <style>
292
320
  * { margin: 0; padding: 0; box-sizing: border-box; }
293
321
  body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #{bgColor}; color: #{textColor}; }
294
322
 
323
+ .toolbar { padding: 8px 16px; border-bottom: 1px solid #{borderColor}; text-align: right; font-size: 13px; }
324
+ .toolbar select { font-size: 13px; padding: 4px 8px; border-radius: 3px; border: 1px solid #{borderColor}; background: #{bgColor}; color: #{textColor}; }
325
+ .toolbar label { color: #888; margin-left: 16px; }
295
326
  .toc { padding: 20px 30px; border-bottom: 1px solid #{borderColor}; }
296
327
  .toc h2 { font-size: 18px; margin-bottom: 10px; }
297
328
  .toc ol { padding-left: 24px; columns: 2; column-gap: 40px; }
@@ -300,17 +331,17 @@ html = """
300
331
  .toc a:hover { text-decoration: underline; }
301
332
  .meta { color: #888; font-size: 12px; }
302
333
 
303
- .file-section { margin-bottom: 0; }
334
+ .file-section { margin-bottom: 0; margin-top: -1px; }
304
335
  .file-header {
305
- background: #{headerBg}; padding: 14px 16px 14px 5.2em; font-size: 13px; font-weight: 600;
306
- border-top: 1px solid #{borderColor}; border-bottom: 1px solid #{borderColor}; margin-top: -1px;
336
+ background: #{headerBg}; padding: 10px 16px 10px 5.85em; font-size: 13px; font-weight: 600;
337
+ border-top: 1px solid #{borderColor}; border-bottom: 1px solid #{borderColor};
307
338
  display: flex; justify-content: space-between; align-items: center;
308
339
  }
309
340
  .nav { font-weight: normal; font-size: 12px; }
310
- .nav a { color: #888; text-decoration: none; margin-left: 12px; }
311
- .nav a:hover { color: #{textColor}; }
341
+ .nav a { color: #888; text-decoration: none; padding: 6px 10px; border-radius: 3px; }
342
+ .nav a:hover { color: #{textColor}; background: #{if dark then '#30363d' else '#e0e0e0'}; }
312
343
 
313
- .code-container { overflow-x: auto; }
344
+ .code-container { overflow-x: auto; border-bottom: 1px solid #{borderColor}; }
314
345
  .code-container pre { margin: 0; border-radius: 0; }
315
346
  .code-container code { font-size: 13px; line-height: 1.5; padding: 0 !important; display: block; }
316
347
  .line-num { color: #aaa; background: #{if dark then '#161b22' else '#f4f4f4'}; user-select: none; display: inline-block; min-width: 2em; text-align: right; padding: 0 0.7em; margin-right: 0.7em; border-right: 1px solid #{borderColor}; }
@@ -318,18 +349,49 @@ html = """
318
349
  @media print {
319
350
  .toc { page-break-after: always; }
320
351
  .file-header { background: #f0f0f0 !important; color: #000 !important; }
352
+ .toolbar { display: none; }
321
353
  .nav { display: none; }
322
- .file-section { page-break-before: always; }
323
- .file-section:first-of-type { page-break-before: avoid; }
324
354
  body { background: white !important; color: black !important; }
325
355
  code { font-size: 11px !important; }
326
356
  }
327
357
  </style>
328
358
  </head>
329
359
  <body>
360
+ <div class="toolbar">
361
+ <label>Theme: <select id="theme-picker">
362
+ <optgroup label="Light">#{themeOptions.split('\n').filter((o) -> o.includes('data-dark="false"')).join('')}</optgroup>
363
+ <optgroup label="Dark">#{themeOptions.split('\n').filter((o) -> o.includes('data-dark="true"')).join('')}</optgroup>
364
+ </select></label>
365
+ </div>
330
366
  <a name="top"></a>
331
367
  #{toc}
332
368
  #{fileSections}
369
+ <script>
370
+ var themes = {
371
+ #{themeData}
372
+ };
373
+ document.getElementById('theme-picker').addEventListener('change', function(e) {
374
+ var id = e.target.value;
375
+ var t = themes[id];
376
+ if (!t) return;
377
+ document.getElementById('hljs-theme').textContent = t.c;
378
+ var d = t.d;
379
+ var bg = d ? '#0d1117' : '#ffffff';
380
+ var fg = d ? '#e6edf3' : '#1f2328';
381
+ var hdr = d ? '#161b22' : '#f6f8fa';
382
+ var brd = d ? '#30363d' : '#d0d7de';
383
+ var gut = d ? '#161b22' : '#f4f4f4';
384
+ document.body.style.background = bg;
385
+ document.body.style.color = fg;
386
+ document.querySelectorAll('.toolbar').forEach(function(e) { e.style.borderColor = brd; });
387
+ document.querySelectorAll('.toolbar select').forEach(function(e) { e.style.background = bg; e.style.color = fg; e.style.borderColor = brd; });
388
+ document.querySelectorAll('.toc').forEach(function(e) { e.style.borderColor = brd; });
389
+ document.querySelectorAll('.file-header').forEach(function(e) { e.style.background = hdr; e.style.borderColor = brd; e.style.color = fg; });
390
+ document.querySelectorAll('.line-num').forEach(function(e) { e.style.background = gut; e.style.borderColor = brd; });
391
+ document.querySelectorAll('.code-container').forEach(function(e) { e.style.borderColor = brd; });
392
+ document.querySelectorAll('.toc a').forEach(function(e) { e.style.color = fg; });
393
+ });
394
+ </script>
333
395
  </body>
334
396
  </html>
335
397
  """