wormclaude 1.0.20 → 1.0.22
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/dist/ansi.js +166 -0
- package/dist/cli.js +20 -71
- package/dist/highlight.js +132 -0
- package/dist/markdown.js +17 -3
- package/dist/theme.js +9 -1
- package/package.json +1 -1
package/dist/ansi.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// Geçmiş öğelerini doğrudan terminal scrollback'ine yazmak için ANSI string üreticisi.
|
|
2
|
+
// ink <Static> resize'da her şeyi yeniden bastığından (kademe/çift banner), geçmiş artık
|
|
3
|
+
// stdout'a BİR KEZ yazılır; ink yalnız canlı footer'ı yönetir. Böylece doğal kaydırma +
|
|
4
|
+
// metin seçip kopyalama çalışır. Markdown + sözdizimi vurgusu burada ANSI'ye çevrilir.
|
|
5
|
+
import { theme } from './theme.js';
|
|
6
|
+
import { highlight } from './highlight.js';
|
|
7
|
+
import { t } from './i18n.js';
|
|
8
|
+
const RESET = '\x1b[0m';
|
|
9
|
+
function hexAnsi(hex) {
|
|
10
|
+
if (!hex)
|
|
11
|
+
return '';
|
|
12
|
+
const m = /^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(hex);
|
|
13
|
+
if (!m)
|
|
14
|
+
return '';
|
|
15
|
+
return `\x1b[38;2;${parseInt(m[1], 16)};${parseInt(m[2], 16)};${parseInt(m[3], 16)}m`;
|
|
16
|
+
}
|
|
17
|
+
const paint = (s, hex, bold = false, dim = false, italic = false) => `${bold ? '\x1b[1m' : ''}${dim ? '\x1b[2m' : ''}${italic ? '\x1b[3m' : ''}${hexAnsi(hex)}${s}${RESET}`;
|
|
18
|
+
// ── Satır kaydırma (görsel genişliğe göre basit sarma) ──
|
|
19
|
+
function wrap(text, width) {
|
|
20
|
+
const out = [];
|
|
21
|
+
for (const para of String(text).split('\n')) {
|
|
22
|
+
if (!para) {
|
|
23
|
+
out.push('');
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
let line = '';
|
|
27
|
+
for (const word of para.split(' ')) {
|
|
28
|
+
const cand = line ? line + ' ' + word : word;
|
|
29
|
+
if (cand.length <= width) {
|
|
30
|
+
line = cand;
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (line)
|
|
34
|
+
out.push(line);
|
|
35
|
+
if (word.length > width) {
|
|
36
|
+
let w = word;
|
|
37
|
+
while (w.length > width) {
|
|
38
|
+
out.push(w.slice(0, width));
|
|
39
|
+
w = w.slice(width);
|
|
40
|
+
}
|
|
41
|
+
line = w;
|
|
42
|
+
}
|
|
43
|
+
else
|
|
44
|
+
line = word;
|
|
45
|
+
}
|
|
46
|
+
out.push(line);
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
// ── Inline markdown → ANSI (**bold** `code` *italik* [t](u)) ──
|
|
51
|
+
function inlineAnsi(text) {
|
|
52
|
+
return text
|
|
53
|
+
.replace(/`([^`]+)`/g, (_m, c) => paint(c, theme.green))
|
|
54
|
+
.replace(/\*\*([^*]+)\*\*/g, (_m, c) => paint(c, undefined, true))
|
|
55
|
+
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_m, a, b) => `${a}${paint(' (' + b + ')', theme.redBright)}`);
|
|
56
|
+
}
|
|
57
|
+
const WORM_ROWS = [
|
|
58
|
+
'██╗ ██╗ ██████╗ ██████╗ ███╗ ███╗',
|
|
59
|
+
'██║ ██║██╔═══██╗██╔══██╗████╗ ████║',
|
|
60
|
+
'██║ █╗ ██║██║ ██║██████╔╝██╔████╔██║',
|
|
61
|
+
'██║███╗██║██║ ██║██╔══██╗██║╚██╔╝██║',
|
|
62
|
+
'╚███╔███╔╝╚██████╔╝██║ ██║██║ ╚═╝ ██║',
|
|
63
|
+
' ╚══╝╚══╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝',
|
|
64
|
+
];
|
|
65
|
+
const CLAUDE_ROWS = [
|
|
66
|
+
' ██████╗██╗ █████╗ ██╗ ██╗██████╗ ███████╗',
|
|
67
|
+
'██╔════╝██║ ██╔══██╗██║ ██║██╔══██╗██╔════╝',
|
|
68
|
+
'██║ ██║ ███████║██║ ██║██║ ██║█████╗ ',
|
|
69
|
+
'██║ ██║ ██╔══██║██║ ██║██║ ██║██╔══╝ ',
|
|
70
|
+
'╚██████╗███████╗██║ ██║╚██████╔╝██████╔╝███████╗',
|
|
71
|
+
' ╚═════╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝',
|
|
72
|
+
];
|
|
73
|
+
/** Banner'ı ANSI olarak üretir (dar ekranda tek kelime, geniş ekranda yan yana). */
|
|
74
|
+
export function bannerAnsi(cols) {
|
|
75
|
+
if (cols < 46)
|
|
76
|
+
return paint('WORMCLAUDE', theme.red, true) + '\n' + paint(' ' + t('banner.subtitle'), theme.greyDim);
|
|
77
|
+
const rows = cols >= 88 ? WORM_ROWS.map((w, i) => w + CLAUDE_ROWS[i]) : [...WORM_ROWS, ...CLAUDE_ROWS];
|
|
78
|
+
return rows.map((r) => paint(r, theme.red, true)).join('\n') + '\n' + paint(' ' + t('banner.subtitle'), theme.greyDim);
|
|
79
|
+
}
|
|
80
|
+
// ── Markdown bloğu → ANSI (kod blokları sözdizimi-vurgulu) ──
|
|
81
|
+
function markdownAnsi(text, cols) {
|
|
82
|
+
const W = Math.max(20, cols - 2);
|
|
83
|
+
const lines = text.split('\n');
|
|
84
|
+
const out = [];
|
|
85
|
+
let inCode = false, fence = '', lang = '', code = [];
|
|
86
|
+
const fenceRe = /^ *(`{3,}|~{3,}) *(\w*)? *$/;
|
|
87
|
+
const flushCode = () => {
|
|
88
|
+
if (lang)
|
|
89
|
+
out.push(paint(' ' + lang, theme.greyDim));
|
|
90
|
+
for (const toks of highlight(code.join('\n'), lang)) {
|
|
91
|
+
out.push(paint('│ ', theme.greyDim) + (toks.length ? toks.map((tk) => paint(tk.text, tk.color)).join('') : ' '));
|
|
92
|
+
}
|
|
93
|
+
code = [];
|
|
94
|
+
lang = '';
|
|
95
|
+
fence = '';
|
|
96
|
+
};
|
|
97
|
+
for (const line of lines) {
|
|
98
|
+
if (inCode) {
|
|
99
|
+
const fm = line.match(fenceRe);
|
|
100
|
+
if (fm && fm[1][0] === fence[0] && fm[1].length >= fence.length) {
|
|
101
|
+
inCode = false;
|
|
102
|
+
flushCode();
|
|
103
|
+
}
|
|
104
|
+
else
|
|
105
|
+
code.push(line);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const fm = line.match(fenceRe);
|
|
109
|
+
if (fm) {
|
|
110
|
+
inCode = true;
|
|
111
|
+
fence = fm[1];
|
|
112
|
+
lang = (fm[2] || '').trim();
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const hm = line.match(/^ *(#{1,4}) +(.*)/);
|
|
116
|
+
const um = line.match(/^([ \t]*)([-*+]) +(.*)/);
|
|
117
|
+
const om = line.match(/^([ \t]*)(\d+)\. +(.*)/);
|
|
118
|
+
if (hm) {
|
|
119
|
+
const lvl = hm[1].length, inner = inlineAnsi(hm[2]);
|
|
120
|
+
out.push(lvl <= 2 ? paint(inner, theme.redBright, true) : lvl === 3 ? paint(inner, theme.white, true) : paint(inner, theme.grey, false, false, true));
|
|
121
|
+
}
|
|
122
|
+
else if (um || om) {
|
|
123
|
+
const [, ws, marker, itemText] = (um || om);
|
|
124
|
+
const prefix = om ? `${marker}. ` : '• ';
|
|
125
|
+
out.push(' '.repeat(ws.length) + paint(prefix, theme.grey) + inlineAnsi(itemText));
|
|
126
|
+
}
|
|
127
|
+
else if (line.trim() === '') {
|
|
128
|
+
out.push('');
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
for (const w of wrap(line, W))
|
|
132
|
+
out.push(inlineAnsi(w));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (inCode)
|
|
136
|
+
flushCode();
|
|
137
|
+
return out.join('\n');
|
|
138
|
+
}
|
|
139
|
+
/** Bir geçmiş öğesini scrollback'e yazılacak ANSI string'e çevirir (üstte 1 boş satır). */
|
|
140
|
+
export function itemAnsi(it, cols) {
|
|
141
|
+
const W = Math.max(24, cols - 2);
|
|
142
|
+
if (it.kind === 'banner')
|
|
143
|
+
return '\n' + bannerAnsi(cols);
|
|
144
|
+
if (it.kind === 'user') {
|
|
145
|
+
const inner = W - 4;
|
|
146
|
+
const top = paint('╭' + '─'.repeat(W - 2) + '╮', theme.greyDim);
|
|
147
|
+
const bot = paint('╰' + '─'.repeat(W - 2) + '╯', theme.greyDim);
|
|
148
|
+
const body = wrap('› ' + (it.text || ''), inner).map((ln) => paint('│ ', theme.greyDim) + paint(ln.padEnd(inner), theme.white) + paint(' │', theme.greyDim));
|
|
149
|
+
return '\n' + [top, ...body, bot].join('\n');
|
|
150
|
+
}
|
|
151
|
+
if (it.kind === 'assistant') {
|
|
152
|
+
const md = markdownAnsi(it.text || '', cols).split('\n');
|
|
153
|
+
return '\n' + md.map((ln, i) => (i === 0 ? paint('⏺ ', theme.redBright, true) + ln : ' ' + ln)).join('\n');
|
|
154
|
+
}
|
|
155
|
+
if (it.kind === 'tool') {
|
|
156
|
+
const n = (it.result || '').split('\n').length, chars = (it.result || '').length;
|
|
157
|
+
const head = paint('⏺ ', theme.redBright, true) + paint(it.label || '', theme.white);
|
|
158
|
+
const sub = paint(' ⎿ ', theme.greyDim) + (it.ok
|
|
159
|
+
? paint(`${n} ${t('common.lines') || 'satır'} (${chars})`, theme.grey)
|
|
160
|
+
: paint('✗ ' + (it.result || '').slice(0, 160), theme.errorRed));
|
|
161
|
+
return '\n' + head + '\n' + sub;
|
|
162
|
+
}
|
|
163
|
+
if (it.kind === 'note')
|
|
164
|
+
return '\n' + wrap(it.text || '', W).map((ln) => paint(ln, theme.greyDim)).join('\n');
|
|
165
|
+
return '';
|
|
166
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import React, { useState, useRef, useEffect } from 'react';
|
|
3
|
-
import { render, Box, Text, useApp, useInput } from 'ink';
|
|
3
|
+
import { render, Box, Text, Static, useApp, useInput } from 'ink';
|
|
4
4
|
import TextInput from 'ink-text-input';
|
|
5
5
|
import * as path from 'node:path';
|
|
6
6
|
import { theme, VERSION } from './theme.js';
|
|
@@ -85,18 +85,10 @@ const _initHistory = () => {
|
|
|
85
85
|
sys.push({ role: 'system', content: _memCtx });
|
|
86
86
|
return sys;
|
|
87
87
|
};
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
// ?1049h alt-screen
|
|
91
|
-
|
|
92
|
-
process.stdout.write('\x1b[?1049h\x1b[?1007h\x1b[2J\x1b[H');
|
|
93
|
-
}
|
|
94
|
-
catch { }
|
|
95
|
-
const _leaveAlt = () => { try {
|
|
96
|
-
process.stdout.write('\x1b[?1007l\x1b[?1049l');
|
|
97
|
-
}
|
|
98
|
-
catch { } };
|
|
99
|
-
process.on('exit', _leaveAlt);
|
|
88
|
+
// NORMAL buffer (alt-screen YOK) — transcript <Static> ile gerçek terminal scrollback'ine
|
|
89
|
+
// basılır. Böylece terminalin doğal kaydırması (fare tekerleği / Shift+PageUp) ve metin
|
|
90
|
+
// seçip kopyalama tam çalışır. Eskiden ?1049h alt-screen kullanılıyordu; o, scrollback'i ve
|
|
91
|
+
// kopyalamayı engelliyordu — kaldırıldı.
|
|
100
92
|
function useDimensions() {
|
|
101
93
|
const [d, setD] = useState({
|
|
102
94
|
cols: process.stdout.columns || 80,
|
|
@@ -448,25 +440,9 @@ function App() {
|
|
|
448
440
|
else if (key.downArrow)
|
|
449
441
|
setCmdSel((s) => (s + 1) % n);
|
|
450
442
|
});
|
|
451
|
-
//
|
|
452
|
-
//
|
|
453
|
-
|
|
454
|
-
if (!started || perm || ask || lang === null)
|
|
455
|
-
return;
|
|
456
|
-
// satır-temelli; üst sınır render'da off=min(scroll,maxScroll) ile kırpılır
|
|
457
|
-
if (key.pageUp)
|
|
458
|
-
setScroll((s) => s + 10);
|
|
459
|
-
else if (key.pageDown)
|
|
460
|
-
setScroll((s) => Math.max(0, s - 10));
|
|
461
|
-
else if (input === '' && !busy) {
|
|
462
|
-
if (key.upArrow)
|
|
463
|
-
setScroll((s) => s + 3);
|
|
464
|
-
else if (key.downArrow)
|
|
465
|
-
setScroll((s) => Math.max(0, s - 3));
|
|
466
|
-
}
|
|
467
|
-
});
|
|
468
|
-
// Yeni mesaj gelince en alta dön
|
|
469
|
-
useEffect(() => { setScroll(0); }, [items.length]);
|
|
443
|
+
// NOT: Transcript artık <Static> ile terminal scrollback'ine basılıyor; kaydırma
|
|
444
|
+
// terminalin kendi işi (fare tekerleği / Shift+PageUp). Tuşları YAKALAMIYORUZ ki
|
|
445
|
+
// doğal kaydırma + metin seçip kopyalama bozulmasın.
|
|
470
446
|
// Başlangıçta güven seviyesini (Doğrulanmış Araştırmacı Programı) çek → StatusLine rozeti
|
|
471
447
|
useEffect(() => {
|
|
472
448
|
if (!started)
|
|
@@ -812,10 +788,9 @@ function App() {
|
|
|
812
788
|
runAgent(v);
|
|
813
789
|
}
|
|
814
790
|
const { cols, rows } = useDimensions();
|
|
815
|
-
// İlk açılış: dil seçimi
|
|
791
|
+
// İlk açılış: dil seçimi (banner stdout'a basıldı; burada yalnız seçim footer'ı)
|
|
816
792
|
if (lang === null) {
|
|
817
|
-
return (React.createElement(Box, { flexDirection: "column",
|
|
818
|
-
React.createElement(Banner, null),
|
|
793
|
+
return (React.createElement(Box, { flexDirection: "column", width: cols },
|
|
819
794
|
React.createElement(Box, { flexDirection: "column", marginTop: 1, paddingX: 1 },
|
|
820
795
|
React.createElement(Text, { color: theme.redBright, bold: true }, t('lang.title')),
|
|
821
796
|
React.createElement(Box, { flexDirection: "column", marginTop: 1 },
|
|
@@ -827,14 +802,11 @@ function App() {
|
|
|
827
802
|
t('lang.en'))),
|
|
828
803
|
React.createElement(Text, { color: theme.greyDim },
|
|
829
804
|
" ",
|
|
830
|
-
t('lang.hint')))
|
|
831
|
-
React.createElement(Box, { flexGrow: 1 }),
|
|
832
|
-
React.createElement(StatusLine, { model: config.model, ctxTokens: 0 })));
|
|
805
|
+
t('lang.hint')))));
|
|
833
806
|
}
|
|
834
|
-
// Açılış: klasöre güven sorusu (
|
|
807
|
+
// Açılış: klasöre güven sorusu (banner stdout'ta; burada yalnız soru footer'ı)
|
|
835
808
|
if (!started) {
|
|
836
|
-
return (React.createElement(Box, { flexDirection: "column",
|
|
837
|
-
React.createElement(Banner, null),
|
|
809
|
+
return (React.createElement(Box, { flexDirection: "column", width: cols },
|
|
838
810
|
React.createElement(Box, { flexDirection: "column", marginTop: 1, paddingX: 1 },
|
|
839
811
|
React.createElement(Text, { color: theme.redBright, bold: true }, t('trust.accessing')),
|
|
840
812
|
React.createElement(Text, null, " "),
|
|
@@ -852,37 +824,14 @@ function App() {
|
|
|
852
824
|
t('trust.no'))),
|
|
853
825
|
React.createElement(Text, { color: theme.greyDim },
|
|
854
826
|
" ",
|
|
855
|
-
t('trust.hint')))
|
|
856
|
-
React.createElement(Box, { flexGrow: 1 }),
|
|
857
|
-
React.createElement(StatusLine, { model: config.model, ctxTokens: 0 })));
|
|
827
|
+
t('trust.hint')))));
|
|
858
828
|
}
|
|
859
|
-
return (React.createElement(Box, { flexDirection: "column",
|
|
860
|
-
React.createElement(
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
const maxScroll = Math.max(0, all.length - avail);
|
|
866
|
-
const off = Math.min(scroll, maxScroll); // alttan kaç satır yukarı
|
|
867
|
-
const startLine = Math.max(0, all.length - avail - off);
|
|
868
|
-
const view = all.slice(startLine, startLine + avail);
|
|
869
|
-
return (React.createElement(React.Fragment, null,
|
|
870
|
-
startLine > 0 ? React.createElement(Text, { color: theme.greyDim },
|
|
871
|
-
" \u2191 ",
|
|
872
|
-
startLine,
|
|
873
|
-
" sat\u0131r \u00B7 PageUp") : null,
|
|
874
|
-
view.map((segs, i) => (React.createElement(Text, { key: i }, segs.length === 0
|
|
875
|
-
? ' '
|
|
876
|
-
: segs.map((s, j) => (React.createElement(Text, { key: j, color: s.dim ? theme.greyDim : s.color, bold: s.bold }, linkify(s.text))))))),
|
|
877
|
-
off > 0 ? React.createElement(Text, { color: theme.greyDim },
|
|
878
|
-
" \u2193 ",
|
|
879
|
-
off,
|
|
880
|
-
" sat\u0131r \u00B7 PageDown / Son") : null,
|
|
881
|
-
streaming && off === 0 ? (React.createElement(Box, { flexDirection: "row" },
|
|
882
|
-
React.createElement(Text, { color: theme.redBright, bold: true }, "\u23FA "),
|
|
883
|
-
React.createElement(Box, { flexDirection: "column" },
|
|
884
|
-
React.createElement(Text, { color: theme.white }, streaming)))) : null));
|
|
885
|
-
})()),
|
|
829
|
+
return (React.createElement(Box, { flexDirection: "column", width: cols },
|
|
830
|
+
React.createElement(Static, { items: items }, (item, i) => React.createElement(RenderItem, { key: i, item: item })),
|
|
831
|
+
streaming ? (React.createElement(Box, { marginTop: 1, flexDirection: "row" },
|
|
832
|
+
React.createElement(Text, { color: theme.redBright, bold: true }, "\u23FA "),
|
|
833
|
+
React.createElement(Box, { flexDirection: "column", flexGrow: 1 },
|
|
834
|
+
React.createElement(MarkdownDisplay, { text: streaming })))) : null,
|
|
886
835
|
perm ? (React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: theme.red, paddingX: 1, marginTop: 1 },
|
|
887
836
|
React.createElement(Text, { color: theme.redBright, bold: true }, t('perm.title', perm.label)),
|
|
888
837
|
permMode === 'feedback' ? (React.createElement(React.Fragment, null,
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// Bağımlılıksız terminal sözdizimi vurgusu (lowlight/highlight.js yerine hafif tokenizer).
|
|
2
|
+
// Kod bloklarını dile göre token'lara böler; her token'a tema rengi atar. Asla patlamaz —
|
|
3
|
+
// tanımadığı dilde ortak kurallarla (string/sayı/yorum/anahtar kelime) renklendirir.
|
|
4
|
+
import { theme } from './theme.js';
|
|
5
|
+
// Dil bazlı anahtar kelime setleri (ortak + spesifik).
|
|
6
|
+
const KW = {
|
|
7
|
+
js: ['const', 'let', 'var', 'function', 'return', 'if', 'else', 'for', 'while', 'do', 'switch', 'case', 'break', 'continue', 'new', 'class', 'extends', 'super', 'this', 'typeof', 'instanceof', 'in', 'of', 'await', 'async', 'yield', 'try', 'catch', 'finally', 'throw', 'import', 'export', 'from', 'default', 'delete', 'void', 'null', 'undefined', 'true', 'false'],
|
|
8
|
+
ts: ['interface', 'type', 'enum', 'implements', 'public', 'private', 'protected', 'readonly', 'abstract', 'namespace', 'declare', 'as', 'keyof', 'infer', 'satisfies'],
|
|
9
|
+
py: ['def', 'class', 'return', 'if', 'elif', 'else', 'for', 'while', 'break', 'continue', 'import', 'from', 'as', 'with', 'try', 'except', 'finally', 'raise', 'lambda', 'yield', 'global', 'nonlocal', 'pass', 'assert', 'del', 'in', 'is', 'not', 'and', 'or', 'None', 'True', 'False', 'self', 'async', 'await'],
|
|
10
|
+
bash: ['if', 'then', 'else', 'elif', 'fi', 'for', 'while', 'do', 'done', 'case', 'esac', 'function', 'in', 'return', 'export', 'local', 'echo', 'cd', 'source', 'set', 'unset'],
|
|
11
|
+
go: ['func', 'package', 'import', 'var', 'const', 'type', 'struct', 'interface', 'map', 'chan', 'go', 'defer', 'return', 'if', 'else', 'for', 'range', 'switch', 'case', 'select', 'break', 'continue', 'nil', 'true', 'false'],
|
|
12
|
+
rust: ['fn', 'let', 'mut', 'const', 'struct', 'enum', 'impl', 'trait', 'pub', 'use', 'mod', 'match', 'if', 'else', 'for', 'while', 'loop', 'return', 'self', 'Self', 'where', 'async', 'await', 'move', 'ref', 'true', 'false'],
|
|
13
|
+
java: ['public', 'private', 'protected', 'class', 'interface', 'extends', 'implements', 'static', 'final', 'void', 'new', 'return', 'if', 'else', 'for', 'while', 'switch', 'case', 'break', 'continue', 'try', 'catch', 'finally', 'throw', 'throws', 'import', 'package', 'this', 'super', 'null', 'true', 'false'],
|
|
14
|
+
css: ['important', 'inherit', 'initial', 'auto', 'none', 'flex', 'grid', 'block', 'inline'],
|
|
15
|
+
};
|
|
16
|
+
function langKeywords(lang) {
|
|
17
|
+
const l = (lang || '').toLowerCase();
|
|
18
|
+
const base = [];
|
|
19
|
+
if (l === 'ts' || l === 'tsx' || l === 'typescript')
|
|
20
|
+
base.push(...KW.js, ...KW.ts);
|
|
21
|
+
else if (l === 'js' || l === 'jsx' || l === 'javascript' || l === 'json' || l === 'node')
|
|
22
|
+
base.push(...KW.js);
|
|
23
|
+
else if (l === 'py' || l === 'python')
|
|
24
|
+
base.push(...KW.py);
|
|
25
|
+
else if (l === 'sh' || l === 'bash' || l === 'shell' || l === 'zsh')
|
|
26
|
+
base.push(...KW.bash);
|
|
27
|
+
else if (l === 'go' || l === 'golang')
|
|
28
|
+
base.push(...KW.go);
|
|
29
|
+
else if (l === 'rs' || l === 'rust')
|
|
30
|
+
base.push(...KW.rust);
|
|
31
|
+
else if (l === 'java')
|
|
32
|
+
base.push(...KW.java);
|
|
33
|
+
else if (l === 'css' || l === 'scss')
|
|
34
|
+
base.push(...KW.css);
|
|
35
|
+
else
|
|
36
|
+
base.push(...KW.js, ...KW.py); // bilinmeyen dil → en yaygın iki set
|
|
37
|
+
return new Set(base);
|
|
38
|
+
}
|
|
39
|
+
const lineComment = (lang) => {
|
|
40
|
+
const l = (lang || '').toLowerCase();
|
|
41
|
+
if (['py', 'python', 'sh', 'bash', 'shell', 'zsh', 'yaml', 'yml', 'rb', 'ruby', 'toml'].includes(l))
|
|
42
|
+
return '#';
|
|
43
|
+
return '//';
|
|
44
|
+
};
|
|
45
|
+
// Bir satırı token'lara böl. Basit ama sağlam bir durum makinesi.
|
|
46
|
+
function tokenizeLine(line, kw, lc) {
|
|
47
|
+
const toks = [];
|
|
48
|
+
let i = 0;
|
|
49
|
+
const n = line.length;
|
|
50
|
+
const push = (text, color) => { if (text)
|
|
51
|
+
toks.push({ text, color }); };
|
|
52
|
+
while (i < n) {
|
|
53
|
+
const ch = line[i];
|
|
54
|
+
// satır yorumu (// veya #)
|
|
55
|
+
if (line.startsWith(lc, i)) {
|
|
56
|
+
push(line.slice(i), theme.synComment);
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
// dizgi: " ' `
|
|
60
|
+
if (ch === '"' || ch === "'" || ch === '`') {
|
|
61
|
+
let j = i + 1;
|
|
62
|
+
while (j < n && line[j] !== ch) {
|
|
63
|
+
if (line[j] === '\\')
|
|
64
|
+
j++;
|
|
65
|
+
j++;
|
|
66
|
+
}
|
|
67
|
+
push(line.slice(i, Math.min(j + 1, n)), theme.synString);
|
|
68
|
+
i = j + 1;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
// sayı
|
|
72
|
+
if (/[0-9]/.test(ch) && (i === 0 || !/[A-Za-z_]/.test(line[i - 1]))) {
|
|
73
|
+
let j = i;
|
|
74
|
+
while (j < n && /[0-9a-fA-FxX._]/.test(line[j]))
|
|
75
|
+
j++;
|
|
76
|
+
push(line.slice(i, j), theme.synNumber);
|
|
77
|
+
i = j;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
// tanımlayıcı / anahtar kelime
|
|
81
|
+
if (/[A-Za-z_$@]/.test(ch)) {
|
|
82
|
+
let j = i;
|
|
83
|
+
while (j < n && /[A-Za-z0-9_$]/.test(line[j]))
|
|
84
|
+
j++;
|
|
85
|
+
const word = line.slice(i, j);
|
|
86
|
+
const after = line[j];
|
|
87
|
+
if (kw.has(word))
|
|
88
|
+
push(word, theme.synKeyword);
|
|
89
|
+
else if (after === '(')
|
|
90
|
+
push(word, theme.synFunc); // fonksiyon çağrısı
|
|
91
|
+
else if (/^[A-Z]/.test(word))
|
|
92
|
+
push(word, theme.synType); // Tip/Sınıf
|
|
93
|
+
else
|
|
94
|
+
push(word); // düz
|
|
95
|
+
i = j;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
// tek karakter (operatör/noktalama) — düz
|
|
99
|
+
push(ch);
|
|
100
|
+
i++;
|
|
101
|
+
}
|
|
102
|
+
return toks;
|
|
103
|
+
}
|
|
104
|
+
/** Kodu satır-satır token dizilerine çevirir. Her satır Tok[]. */
|
|
105
|
+
export function highlight(code, lang = '') {
|
|
106
|
+
try {
|
|
107
|
+
const kw = langKeywords(lang);
|
|
108
|
+
const lc = lineComment(lang);
|
|
109
|
+
let blockComment = false; // /* ... */ için
|
|
110
|
+
return code.split('\n').map((line) => {
|
|
111
|
+
// çok-satırlı /* */ yorum bloğu (basit destek)
|
|
112
|
+
if (blockComment) {
|
|
113
|
+
const end = line.indexOf('*/');
|
|
114
|
+
if (end === -1)
|
|
115
|
+
return [{ text: line, color: theme.synComment }];
|
|
116
|
+
blockComment = false;
|
|
117
|
+
const rest = tokenizeLine(line.slice(end + 2), kw, lc);
|
|
118
|
+
return [{ text: line.slice(0, end + 2), color: theme.synComment }, ...rest];
|
|
119
|
+
}
|
|
120
|
+
const start = line.indexOf('/*');
|
|
121
|
+
if (start !== -1 && line.indexOf('*/', start) === -1) {
|
|
122
|
+
blockComment = true;
|
|
123
|
+
const pre = tokenizeLine(line.slice(0, start), kw, lc);
|
|
124
|
+
return [...pre, { text: line.slice(start), color: theme.synComment }];
|
|
125
|
+
}
|
|
126
|
+
return tokenizeLine(line, kw, lc);
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return code.split('\n').map((l) => [{ text: l, color: theme.synString }]);
|
|
131
|
+
}
|
|
132
|
+
}
|
package/dist/markdown.js
CHANGED
|
@@ -4,6 +4,18 @@ import React from 'react';
|
|
|
4
4
|
import { Text, Box } from 'ink';
|
|
5
5
|
import stringWidth from 'string-width';
|
|
6
6
|
import { theme } from './theme.js';
|
|
7
|
+
import { highlight } from './highlight.js';
|
|
8
|
+
// Kod bloğunu sözdizimi-vurgulu render eder (sol kenarda ince kırmızı çubuk + dil etiketi).
|
|
9
|
+
function CodeBlock({ code, lang }) {
|
|
10
|
+
const hl = highlight(code.join('\n'), lang);
|
|
11
|
+
return (React.createElement(Box, { flexDirection: "column", marginY: 1 },
|
|
12
|
+
lang ? React.createElement(Text, { color: theme.greyDim },
|
|
13
|
+
' ',
|
|
14
|
+
lang) : null,
|
|
15
|
+
hl.map((toks, i) => (React.createElement(Box, { key: i },
|
|
16
|
+
React.createElement(Text, { color: theme.greyDim }, "\u2502 "),
|
|
17
|
+
React.createElement(Text, null, toks.length ? toks.map((tk, j) => React.createElement(Text, { key: j, color: tk.color }, tk.text)) : ' '))))));
|
|
18
|
+
}
|
|
7
19
|
// ── Inline markdown: **bold** *italik* ~~strike~~ `code` [text](url) <u>u</u> url ──
|
|
8
20
|
const INLINE_RE = /(\*\*.*?\*\*|\*.*?\*|_.*?_|~~.*?~~|\[.*?\]\(.*?\)|`+.+?`+|<u>.*?<\/u>|https?:\/\/\S+)/g;
|
|
9
21
|
export function RenderInline({ text }) {
|
|
@@ -122,7 +134,7 @@ export function MarkdownDisplay({ text, width }) {
|
|
|
122
134
|
const blocks = [];
|
|
123
135
|
let key = 0;
|
|
124
136
|
const push = (n) => blocks.push(React.createElement(React.Fragment, { key: key++ }, n));
|
|
125
|
-
let inCode = false, codeFence = '', codeLines = [];
|
|
137
|
+
let inCode = false, codeFence = '', codeLang = '', codeLines = [];
|
|
126
138
|
let inTable = false, tHeaders = [], tRows = [];
|
|
127
139
|
const flushTable = () => { if (tHeaders.length)
|
|
128
140
|
push(React.createElement(Table, { headers: tHeaders, rows: tRows, width: W })); inTable = false; tHeaders = []; tRows = []; };
|
|
@@ -137,9 +149,10 @@ export function MarkdownDisplay({ text, width }) {
|
|
|
137
149
|
if (inCode) {
|
|
138
150
|
const fm = line.match(fenceRe);
|
|
139
151
|
if (fm && fm[1][0] === codeFence[0] && fm[1].length >= codeFence.length) {
|
|
140
|
-
push(React.createElement(
|
|
152
|
+
push(React.createElement(CodeBlock, { code: codeLines, lang: codeLang }));
|
|
141
153
|
inCode = false;
|
|
142
154
|
codeFence = '';
|
|
155
|
+
codeLang = '';
|
|
143
156
|
codeLines = [];
|
|
144
157
|
}
|
|
145
158
|
else
|
|
@@ -153,6 +166,7 @@ export function MarkdownDisplay({ text, width }) {
|
|
|
153
166
|
flushTable();
|
|
154
167
|
inCode = true;
|
|
155
168
|
codeFence = fm[1];
|
|
169
|
+
codeLang = (fm[2] || '').trim();
|
|
156
170
|
return;
|
|
157
171
|
}
|
|
158
172
|
if (rm && !inTable) {
|
|
@@ -214,7 +228,7 @@ export function MarkdownDisplay({ text, width }) {
|
|
|
214
228
|
React.createElement(RenderInline, { text: line })));
|
|
215
229
|
});
|
|
216
230
|
if (inCode)
|
|
217
|
-
push(React.createElement(
|
|
231
|
+
push(React.createElement(CodeBlock, { code: codeLines, lang: codeLang }));
|
|
218
232
|
if (inTable)
|
|
219
233
|
flushTable();
|
|
220
234
|
return React.createElement(Box, { flexDirection: "column" }, blocks);
|
package/dist/theme.js
CHANGED
|
@@ -7,5 +7,13 @@ export const theme = {
|
|
|
7
7
|
greyDim: '#666666',
|
|
8
8
|
green: '#4ade80',
|
|
9
9
|
errorRed: '#ff6b6b',
|
|
10
|
+
// ── Sözdizimi vurgusu (kod blokları) ──
|
|
11
|
+
synKeyword: '#ff5c5c', // anahtar kelimeler (if, function, def…) — marka kırmızısı
|
|
12
|
+
synString: '#4ade80', // dizgiler "…" '…' `…`
|
|
13
|
+
synNumber: '#f0b429', // sayılar
|
|
14
|
+
synComment: '#666666', // yorumlar
|
|
15
|
+
synFunc: '#4fc3f7', // fonksiyon/metot adları
|
|
16
|
+
synType: '#a78bfa', // tip/sınıf adları, sabitler
|
|
17
|
+
synProp: '#e0e0e0', // özellik/anahtar adları
|
|
10
18
|
};
|
|
11
|
-
export const VERSION = '1.0.
|
|
19
|
+
export const VERSION = '1.0.22';
|