codeep 1.1.19 → 1.1.21
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/renderer/App.d.ts +11 -3
- package/dist/renderer/App.js +192 -58
- package/dist/renderer/Screen.js +18 -3
- package/dist/renderer/ansi.d.ts +9 -0
- package/dist/renderer/ansi.js +84 -8
- package/dist/renderer/index.d.ts +1 -1
- package/dist/renderer/main.js +7 -0
- package/package.json +1 -1
package/dist/renderer/App.d.ts
CHANGED
|
@@ -8,7 +8,6 @@ export interface Message {
|
|
|
8
8
|
role: 'user' | 'assistant' | 'system';
|
|
9
9
|
content: string;
|
|
10
10
|
}
|
|
11
|
-
export type AppScreen = 'chat' | 'status';
|
|
12
11
|
export interface ConfirmOptions {
|
|
13
12
|
title: string;
|
|
14
13
|
message: string[];
|
|
@@ -34,7 +33,6 @@ export declare class App {
|
|
|
34
33
|
private streamingContent;
|
|
35
34
|
private isStreaming;
|
|
36
35
|
private isLoading;
|
|
37
|
-
private currentScreen;
|
|
38
36
|
private options;
|
|
39
37
|
private scrollOffset;
|
|
40
38
|
private notification;
|
|
@@ -49,6 +47,7 @@ export declare class App {
|
|
|
49
47
|
private pasteInfoOpen;
|
|
50
48
|
private helpOpen;
|
|
51
49
|
private helpScrollIndex;
|
|
50
|
+
private statusOpen;
|
|
52
51
|
private settingsState;
|
|
53
52
|
private showAutocomplete;
|
|
54
53
|
private autocompleteIndex;
|
|
@@ -278,6 +277,10 @@ export declare class App {
|
|
|
278
277
|
* Update autocomplete suggestions
|
|
279
278
|
*/
|
|
280
279
|
private updateAutocomplete;
|
|
280
|
+
/**
|
|
281
|
+
* Handle inline status keys
|
|
282
|
+
*/
|
|
283
|
+
private handleInlineStatusKey;
|
|
281
284
|
/**
|
|
282
285
|
* Handle help screen keys
|
|
283
286
|
*/
|
|
@@ -347,6 +350,7 @@ export declare class App {
|
|
|
347
350
|
/**
|
|
348
351
|
* Render inline help below status bar
|
|
349
352
|
*/
|
|
353
|
+
private renderInlineStatus;
|
|
350
354
|
private renderInlineHelp;
|
|
351
355
|
/**
|
|
352
356
|
* Render inline autocomplete below status bar
|
|
@@ -393,7 +397,11 @@ export declare class App {
|
|
|
393
397
|
*/
|
|
394
398
|
private formatMessage;
|
|
395
399
|
/**
|
|
396
|
-
*
|
|
400
|
+
* Apply inline markdown formatting (bold, italic, inline code) to a line
|
|
401
|
+
*/
|
|
402
|
+
private applyInlineMarkdown;
|
|
403
|
+
/**
|
|
404
|
+
* Format plain text lines with markdown support
|
|
397
405
|
*/
|
|
398
406
|
private formatTextLines;
|
|
399
407
|
/**
|
package/dist/renderer/App.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { Screen } from './Screen.js';
|
|
6
6
|
import { Input, LineEditor } from './Input.js';
|
|
7
|
-
import { fg, style } from './ansi.js';
|
|
7
|
+
import { fg, style, stringWidth } from './ansi.js';
|
|
8
8
|
import clipboardy from 'clipboardy';
|
|
9
9
|
// Primary color: #f02a30 (Codeep red)
|
|
10
10
|
const PRIMARY_COLOR = fg.rgb(240, 42, 48);
|
|
@@ -202,7 +202,6 @@ const COMMAND_DESCRIPTIONS = {
|
|
|
202
202
|
'learn': 'Learn code preferences',
|
|
203
203
|
};
|
|
204
204
|
import { helpCategories, keyboardShortcuts } from './components/Help.js';
|
|
205
|
-
import { renderStatusScreen } from './components/Status.js';
|
|
206
205
|
import { handleSettingsKey, SETTINGS } from './components/Settings.js';
|
|
207
206
|
export class App {
|
|
208
207
|
screen;
|
|
@@ -212,7 +211,6 @@ export class App {
|
|
|
212
211
|
streamingContent = '';
|
|
213
212
|
isStreaming = false;
|
|
214
213
|
isLoading = false;
|
|
215
|
-
currentScreen = 'chat';
|
|
216
214
|
options;
|
|
217
215
|
scrollOffset = 0;
|
|
218
216
|
notification = '';
|
|
@@ -231,6 +229,8 @@ export class App {
|
|
|
231
229
|
// Inline help state
|
|
232
230
|
helpOpen = false;
|
|
233
231
|
helpScrollIndex = 0;
|
|
232
|
+
// Inline status state
|
|
233
|
+
statusOpen = false;
|
|
234
234
|
// Settings screen state
|
|
235
235
|
settingsState = {
|
|
236
236
|
selectedIndex: 0,
|
|
@@ -784,19 +784,7 @@ export class App {
|
|
|
784
784
|
this.options.onExit();
|
|
785
785
|
return;
|
|
786
786
|
}
|
|
787
|
-
|
|
788
|
-
switch (this.currentScreen) {
|
|
789
|
-
case 'status':
|
|
790
|
-
if (event.key === 'escape' || event.key === 'q') {
|
|
791
|
-
this.currentScreen = 'chat';
|
|
792
|
-
this.render();
|
|
793
|
-
}
|
|
794
|
-
break;
|
|
795
|
-
case 'chat':
|
|
796
|
-
default:
|
|
797
|
-
this.handleChatKey(event);
|
|
798
|
-
break;
|
|
799
|
-
}
|
|
787
|
+
this.handleChatKey(event);
|
|
800
788
|
}
|
|
801
789
|
/**
|
|
802
790
|
* Handle chat screen keys
|
|
@@ -822,6 +810,11 @@ export class App {
|
|
|
822
810
|
this.handleInlineConfirmKey(event);
|
|
823
811
|
return;
|
|
824
812
|
}
|
|
813
|
+
// If status is open, handle status keys first
|
|
814
|
+
if (this.statusOpen) {
|
|
815
|
+
this.handleInlineStatusKey(event);
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
825
818
|
// If help is open, handle help keys first
|
|
826
819
|
if (this.helpOpen) {
|
|
827
820
|
this.handleInlineHelpKey(event);
|
|
@@ -1036,6 +1029,15 @@ export class App {
|
|
|
1036
1029
|
this.autocompleteItems = [];
|
|
1037
1030
|
}
|
|
1038
1031
|
}
|
|
1032
|
+
/**
|
|
1033
|
+
* Handle inline status keys
|
|
1034
|
+
*/
|
|
1035
|
+
handleInlineStatusKey(event) {
|
|
1036
|
+
if (event.key === 'escape' || event.key === 'q') {
|
|
1037
|
+
this.statusOpen = false;
|
|
1038
|
+
this.render();
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1039
1041
|
/**
|
|
1040
1042
|
* Handle help screen keys
|
|
1041
1043
|
*/
|
|
@@ -1515,7 +1517,7 @@ export class App {
|
|
|
1515
1517
|
this.render();
|
|
1516
1518
|
break;
|
|
1517
1519
|
case 'status':
|
|
1518
|
-
this.
|
|
1520
|
+
this.statusOpen = true;
|
|
1519
1521
|
this.render();
|
|
1520
1522
|
break;
|
|
1521
1523
|
case 'clear':
|
|
@@ -1542,15 +1544,7 @@ export class App {
|
|
|
1542
1544
|
this.renderIntro();
|
|
1543
1545
|
return;
|
|
1544
1546
|
}
|
|
1545
|
-
|
|
1546
|
-
case 'status':
|
|
1547
|
-
renderStatusScreen(this.screen, this.options.getStatus());
|
|
1548
|
-
break;
|
|
1549
|
-
case 'chat':
|
|
1550
|
-
default:
|
|
1551
|
-
this.renderChat();
|
|
1552
|
-
break;
|
|
1553
|
-
}
|
|
1547
|
+
this.renderChat();
|
|
1554
1548
|
}
|
|
1555
1549
|
/**
|
|
1556
1550
|
* Render chat screen
|
|
@@ -1603,31 +1597,12 @@ export class App {
|
|
|
1603
1597
|
bottomPanelHeight = Math.min(this.autocompleteItems.length + 3, 12);
|
|
1604
1598
|
}
|
|
1605
1599
|
const mainHeight = height - bottomPanelHeight;
|
|
1606
|
-
// Determine if we have enough space for logo (need at least 20 lines)
|
|
1607
|
-
const showLogo = height >= 24;
|
|
1608
|
-
const logoSpace = showLogo ? LOGO_HEIGHT + 1 : 1; // +1 for tagline or simple header
|
|
1609
1600
|
// Layout - main UI takes top portion
|
|
1610
|
-
const
|
|
1611
|
-
const messagesStart = logoSpace;
|
|
1601
|
+
const messagesStart = 0;
|
|
1612
1602
|
const messagesEnd = mainHeight - 4;
|
|
1613
1603
|
const separatorLine = mainHeight - 3;
|
|
1614
1604
|
const inputLine = mainHeight - 2;
|
|
1615
1605
|
const statusLine = mainHeight - 1;
|
|
1616
|
-
// Header - show logo if space permits, otherwise simple text
|
|
1617
|
-
if (showLogo) {
|
|
1618
|
-
// Center the logo
|
|
1619
|
-
const logoWidth = LOGO_LINES[0].length;
|
|
1620
|
-
const logoX = Math.max(0, Math.floor((width - logoWidth) / 2));
|
|
1621
|
-
for (let i = 0; i < LOGO_LINES.length; i++) {
|
|
1622
|
-
this.screen.write(logoX, headerLine + i, LOGO_LINES[i], PRIMARY_COLOR);
|
|
1623
|
-
}
|
|
1624
|
-
}
|
|
1625
|
-
else {
|
|
1626
|
-
// Simple header for small terminals
|
|
1627
|
-
const header = ' Codeep ';
|
|
1628
|
-
const headerPadding = '─'.repeat(Math.max(0, (width - header.length) / 2));
|
|
1629
|
-
this.screen.writeLine(headerLine, headerPadding + header + headerPadding, PRIMARY_COLOR);
|
|
1630
|
-
}
|
|
1631
1606
|
// Messages
|
|
1632
1607
|
const messagesHeight = messagesEnd - messagesStart + 1;
|
|
1633
1608
|
const messagesToRender = this.getVisibleMessages(messagesHeight, width - 2);
|
|
@@ -1662,6 +1637,10 @@ export class App {
|
|
|
1662
1637
|
if (this.helpOpen) {
|
|
1663
1638
|
this.renderInlineHelp(statusLine + 1, width, height - statusLine - 1);
|
|
1664
1639
|
}
|
|
1640
|
+
// Inline status renders BELOW status bar
|
|
1641
|
+
if (this.statusOpen) {
|
|
1642
|
+
this.renderInlineStatus(statusLine + 1, width);
|
|
1643
|
+
}
|
|
1665
1644
|
// Inline search renders BELOW status bar
|
|
1666
1645
|
if (this.searchOpen) {
|
|
1667
1646
|
this.renderInlineSearch(statusLine + 1, width, height - statusLine - 1);
|
|
@@ -1946,6 +1925,35 @@ export class App {
|
|
|
1946
1925
|
/**
|
|
1947
1926
|
* Render inline help below status bar
|
|
1948
1927
|
*/
|
|
1928
|
+
renderInlineStatus(startY, width) {
|
|
1929
|
+
const status = this.options.getStatus();
|
|
1930
|
+
let y = startY;
|
|
1931
|
+
// Separator line
|
|
1932
|
+
this.screen.horizontalLine(y++, '─', PRIMARY_COLOR);
|
|
1933
|
+
// Title
|
|
1934
|
+
this.screen.writeLine(y++, 'Status', PRIMARY_COLOR + style.bold);
|
|
1935
|
+
const items = [
|
|
1936
|
+
{ label: 'Version', value: 'v' + status.version, color: fg.white },
|
|
1937
|
+
{ label: 'Provider', value: status.provider, color: fg.white },
|
|
1938
|
+
{ label: 'Model', value: status.model, color: fg.white },
|
|
1939
|
+
{ label: 'Agent Mode', value: status.agentMode.toUpperCase(), color: status.agentMode === 'on' ? fg.green : status.agentMode === 'manual' ? fg.yellow : fg.gray },
|
|
1940
|
+
{ label: 'Project', value: status.projectPath, color: fg.white },
|
|
1941
|
+
{ label: 'Write Access', value: status.hasWriteAccess ? 'Yes' : 'No', color: status.hasWriteAccess ? fg.green : fg.red },
|
|
1942
|
+
{ label: 'Session', value: status.sessionId || 'New', color: fg.white },
|
|
1943
|
+
{ label: 'Messages', value: status.messageCount.toString(), color: fg.white },
|
|
1944
|
+
{ label: 'Platform', value: process.platform, color: fg.white },
|
|
1945
|
+
{ label: 'Node', value: process.version, color: fg.white },
|
|
1946
|
+
{ label: 'Terminal', value: width + 'x' + this.screen.getSize().height, color: fg.white },
|
|
1947
|
+
];
|
|
1948
|
+
const labelWidth = Math.max(...items.map(i => i.label.length)) + 2;
|
|
1949
|
+
for (const item of items) {
|
|
1950
|
+
this.screen.write(2, y, item.label + ':', fg.gray);
|
|
1951
|
+
this.screen.write(2 + labelWidth, y, item.value, item.color);
|
|
1952
|
+
y++;
|
|
1953
|
+
}
|
|
1954
|
+
y++;
|
|
1955
|
+
this.screen.writeLine(y, 'Esc close', fg.gray);
|
|
1956
|
+
}
|
|
1949
1957
|
renderInlineHelp(startY, width, availableHeight) {
|
|
1950
1958
|
// Build all help items
|
|
1951
1959
|
const allItems = [];
|
|
@@ -2326,6 +2334,20 @@ export class App {
|
|
|
2326
2334
|
*/
|
|
2327
2335
|
getVisibleMessages(height, width) {
|
|
2328
2336
|
const allLines = [];
|
|
2337
|
+
// Logo at the top, scrolls with content
|
|
2338
|
+
if (height >= 20) {
|
|
2339
|
+
const logoWidth = LOGO_LINES[0].length;
|
|
2340
|
+
const logoX = Math.max(0, Math.floor((width - logoWidth) / 2));
|
|
2341
|
+
const pad = ' '.repeat(logoX);
|
|
2342
|
+
for (const line of LOGO_LINES) {
|
|
2343
|
+
allLines.push({ text: pad + line, style: PRIMARY_COLOR, raw: false });
|
|
2344
|
+
}
|
|
2345
|
+
allLines.push({ text: '', style: '' });
|
|
2346
|
+
}
|
|
2347
|
+
else {
|
|
2348
|
+
allLines.push({ text: ' Codeep', style: PRIMARY_COLOR, raw: false });
|
|
2349
|
+
allLines.push({ text: '', style: '' });
|
|
2350
|
+
}
|
|
2329
2351
|
for (const msg of this.messages) {
|
|
2330
2352
|
const msgLines = this.formatMessage(msg.role, msg.content, width);
|
|
2331
2353
|
allLines.push(...msgLines);
|
|
@@ -2335,10 +2357,7 @@ export class App {
|
|
|
2335
2357
|
allLines.push(...streamLines);
|
|
2336
2358
|
}
|
|
2337
2359
|
// Calculate visible window based on scroll offset
|
|
2338
|
-
// scrollOffset=0 means show the most recent (bottom) lines
|
|
2339
|
-
// scrollOffset>0 means scroll up to see older messages
|
|
2340
2360
|
const totalLines = allLines.length;
|
|
2341
|
-
// Clamp scrollOffset to valid range
|
|
2342
2361
|
const maxScroll = Math.max(0, totalLines - height);
|
|
2343
2362
|
if (this.scrollOffset > maxScroll) {
|
|
2344
2363
|
this.scrollOffset = maxScroll;
|
|
@@ -2385,7 +2404,64 @@ export class App {
|
|
|
2385
2404
|
return lines;
|
|
2386
2405
|
}
|
|
2387
2406
|
/**
|
|
2388
|
-
*
|
|
2407
|
+
* Apply inline markdown formatting (bold, italic, inline code) to a line
|
|
2408
|
+
*/
|
|
2409
|
+
applyInlineMarkdown(text) {
|
|
2410
|
+
let result = '';
|
|
2411
|
+
let hasFormatting = false;
|
|
2412
|
+
let i = 0;
|
|
2413
|
+
while (i < text.length) {
|
|
2414
|
+
// Inline code: `code`
|
|
2415
|
+
if (text[i] === '`' && text[i + 1] !== '`') {
|
|
2416
|
+
const end = text.indexOf('`', i + 1);
|
|
2417
|
+
if (end !== -1) {
|
|
2418
|
+
const code = text.slice(i + 1, end);
|
|
2419
|
+
result += fg.rgb(209, 154, 102) + code + '\x1b[0m';
|
|
2420
|
+
hasFormatting = true;
|
|
2421
|
+
i = end + 1;
|
|
2422
|
+
continue;
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
// Bold + italic: ***text***
|
|
2426
|
+
if (text.slice(i, i + 3) === '***') {
|
|
2427
|
+
const end = text.indexOf('***', i + 3);
|
|
2428
|
+
if (end !== -1) {
|
|
2429
|
+
const inner = text.slice(i + 3, end);
|
|
2430
|
+
result += style.bold + style.italic + fg.white + inner + '\x1b[0m';
|
|
2431
|
+
hasFormatting = true;
|
|
2432
|
+
i = end + 3;
|
|
2433
|
+
continue;
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
// Bold: **text**
|
|
2437
|
+
if (text.slice(i, i + 2) === '**') {
|
|
2438
|
+
const end = text.indexOf('**', i + 2);
|
|
2439
|
+
if (end !== -1) {
|
|
2440
|
+
const inner = text.slice(i + 2, end);
|
|
2441
|
+
result += style.bold + fg.white + inner + '\x1b[0m';
|
|
2442
|
+
hasFormatting = true;
|
|
2443
|
+
i = end + 2;
|
|
2444
|
+
continue;
|
|
2445
|
+
}
|
|
2446
|
+
}
|
|
2447
|
+
// Italic: *text*
|
|
2448
|
+
if (text[i] === '*' && text[i + 1] !== '*') {
|
|
2449
|
+
const end = text.indexOf('*', i + 1);
|
|
2450
|
+
if (end !== -1 && end > i + 1) {
|
|
2451
|
+
const inner = text.slice(i + 1, end);
|
|
2452
|
+
result += style.italic + inner + '\x1b[0m';
|
|
2453
|
+
hasFormatting = true;
|
|
2454
|
+
i = end + 1;
|
|
2455
|
+
continue;
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
2458
|
+
result += text[i];
|
|
2459
|
+
i++;
|
|
2460
|
+
}
|
|
2461
|
+
return { formatted: result, hasFormatting };
|
|
2462
|
+
}
|
|
2463
|
+
/**
|
|
2464
|
+
* Format plain text lines with markdown support
|
|
2389
2465
|
*/
|
|
2390
2466
|
formatTextLines(text, maxWidth, firstPrefix, firstStyle) {
|
|
2391
2467
|
const lines = [];
|
|
@@ -2394,21 +2470,79 @@ export class App {
|
|
|
2394
2470
|
const line = contentLines[i];
|
|
2395
2471
|
const prefix = i === 0 ? firstPrefix : ' ';
|
|
2396
2472
|
const prefixStyle = i === 0 ? firstStyle : '';
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2473
|
+
// Heading: ## or ### etc.
|
|
2474
|
+
const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
|
|
2475
|
+
if (headingMatch) {
|
|
2476
|
+
const level = headingMatch[1].length;
|
|
2477
|
+
const headingText = headingMatch[2];
|
|
2478
|
+
const headingColor = level <= 2 ? fg.rgb(97, 175, 239) : fg.rgb(198, 120, 221);
|
|
2479
|
+
lines.push({
|
|
2480
|
+
text: prefix + headingColor + style.bold + headingText + '\x1b[0m',
|
|
2481
|
+
style: prefixStyle,
|
|
2482
|
+
raw: true,
|
|
2483
|
+
});
|
|
2484
|
+
continue;
|
|
2485
|
+
}
|
|
2486
|
+
// Horizontal rule: --- or *** or ___
|
|
2487
|
+
if (/^[-*_]{3,}\s*$/.test(line)) {
|
|
2488
|
+
const ruleWidth = Math.min(maxWidth - 4, 40);
|
|
2489
|
+
lines.push({
|
|
2490
|
+
text: prefix + fg.gray + '─'.repeat(ruleWidth) + '\x1b[0m',
|
|
2491
|
+
style: prefixStyle,
|
|
2492
|
+
raw: true,
|
|
2493
|
+
});
|
|
2494
|
+
continue;
|
|
2495
|
+
}
|
|
2496
|
+
// List items: - item or * item or numbered 1. item
|
|
2497
|
+
const listMatch = line.match(/^(\s*)([-*]|\d+\.)\s+(.+)$/);
|
|
2498
|
+
if (listMatch) {
|
|
2499
|
+
const indent = listMatch[1];
|
|
2500
|
+
const bullet = listMatch[2];
|
|
2501
|
+
const content = listMatch[3];
|
|
2502
|
+
const { formatted, hasFormatting } = this.applyInlineMarkdown(content);
|
|
2503
|
+
const bulletChar = bullet === '-' || bullet === '*' ? '•' : bullet;
|
|
2504
|
+
if (hasFormatting) {
|
|
2505
|
+
lines.push({
|
|
2506
|
+
text: prefix + indent + fg.gray + bulletChar + '\x1b[0m' + ' ' + formatted,
|
|
2507
|
+
style: prefixStyle,
|
|
2508
|
+
raw: true,
|
|
2509
|
+
});
|
|
2510
|
+
}
|
|
2511
|
+
else {
|
|
2400
2512
|
lines.push({
|
|
2401
|
-
text:
|
|
2402
|
-
style:
|
|
2513
|
+
text: prefix + indent + bulletChar + ' ' + content,
|
|
2514
|
+
style: prefixStyle,
|
|
2403
2515
|
});
|
|
2404
2516
|
}
|
|
2517
|
+
continue;
|
|
2405
2518
|
}
|
|
2406
|
-
|
|
2519
|
+
// Regular text with possible inline markdown
|
|
2520
|
+
const { formatted, hasFormatting } = this.applyInlineMarkdown(line);
|
|
2521
|
+
if (hasFormatting) {
|
|
2407
2522
|
lines.push({
|
|
2408
|
-
text: prefix +
|
|
2523
|
+
text: prefix + formatted,
|
|
2409
2524
|
style: prefixStyle,
|
|
2525
|
+
raw: true,
|
|
2410
2526
|
});
|
|
2411
2527
|
}
|
|
2528
|
+
else {
|
|
2529
|
+
// Plain text - word wrap as before
|
|
2530
|
+
if (stringWidth(line) > maxWidth - prefix.length) {
|
|
2531
|
+
const wrapped = this.wordWrap(line, maxWidth - prefix.length);
|
|
2532
|
+
for (let j = 0; j < wrapped.length; j++) {
|
|
2533
|
+
lines.push({
|
|
2534
|
+
text: (j === 0 ? prefix : ' ') + wrapped[j],
|
|
2535
|
+
style: j === 0 ? prefixStyle : '',
|
|
2536
|
+
});
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2539
|
+
else {
|
|
2540
|
+
lines.push({
|
|
2541
|
+
text: prefix + line,
|
|
2542
|
+
style: prefixStyle,
|
|
2543
|
+
});
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2412
2546
|
}
|
|
2413
2547
|
return lines;
|
|
2414
2548
|
}
|
|
@@ -2675,7 +2809,7 @@ export class App {
|
|
|
2675
2809
|
const lines = [];
|
|
2676
2810
|
let currentLine = '';
|
|
2677
2811
|
for (const word of words) {
|
|
2678
|
-
if (currentLine
|
|
2812
|
+
if (stringWidth(currentLine) + stringWidth(word) + 1 > maxWidth && currentLine) {
|
|
2679
2813
|
lines.push(currentLine);
|
|
2680
2814
|
currentLine = word;
|
|
2681
2815
|
}
|
package/dist/renderer/Screen.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Screen buffer with diff-based rendering
|
|
3
3
|
* Only writes changes to terminal - minimizes flickering
|
|
4
4
|
*/
|
|
5
|
-
import { cursor, screen, style, visibleLength } from './ansi.js';
|
|
5
|
+
import { cursor, screen, style, visibleLength, charWidth } from './ansi.js';
|
|
6
6
|
export class Screen {
|
|
7
7
|
width;
|
|
8
8
|
height;
|
|
@@ -73,10 +73,15 @@ export class Screen {
|
|
|
73
73
|
break;
|
|
74
74
|
}
|
|
75
75
|
else {
|
|
76
|
+
const w = charWidth(char);
|
|
76
77
|
if (col >= 0 && col < this.width) {
|
|
77
78
|
this.buffer[y][col] = { char, style: currentStyle };
|
|
79
|
+
// Wide char: fill next cell with empty placeholder
|
|
80
|
+
if (w === 2 && col + 1 < this.width) {
|
|
81
|
+
this.buffer[y][col + 1] = { char: '', style: currentStyle };
|
|
82
|
+
}
|
|
78
83
|
}
|
|
79
|
-
col
|
|
84
|
+
col += w;
|
|
80
85
|
}
|
|
81
86
|
}
|
|
82
87
|
}
|
|
@@ -132,10 +137,15 @@ export class Screen {
|
|
|
132
137
|
}
|
|
133
138
|
else {
|
|
134
139
|
// Regular character
|
|
140
|
+
const w = charWidth(text[i]);
|
|
135
141
|
if (col < this.width) {
|
|
136
142
|
this.buffer[y][col] = { char: text[i], style: currentStyle };
|
|
137
|
-
|
|
143
|
+
// Wide char: fill next cell with empty placeholder
|
|
144
|
+
if (w === 2 && col + 1 < this.width) {
|
|
145
|
+
this.buffer[y][col + 1] = { char: '', style: currentStyle };
|
|
146
|
+
}
|
|
138
147
|
}
|
|
148
|
+
col += w;
|
|
139
149
|
i++;
|
|
140
150
|
}
|
|
141
151
|
}
|
|
@@ -216,6 +226,11 @@ export class Screen {
|
|
|
216
226
|
if (cell.char === renderedCell.char && cell.style === renderedCell.style) {
|
|
217
227
|
continue;
|
|
218
228
|
}
|
|
229
|
+
// Skip wide-char placeholder cells
|
|
230
|
+
if (cell.char === '') {
|
|
231
|
+
this.rendered[y][x] = { ...cell };
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
219
234
|
// Move cursor and write
|
|
220
235
|
output += cursor.to(y + 1, x + 1);
|
|
221
236
|
if (cell.style !== lastStyle) {
|
package/dist/renderer/ansi.d.ts
CHANGED
|
@@ -81,6 +81,15 @@ export declare const style: {
|
|
|
81
81
|
* Helper to create styled text
|
|
82
82
|
*/
|
|
83
83
|
export declare function styled(text: string, ...styles: string[]): string;
|
|
84
|
+
/**
|
|
85
|
+
* Get terminal display width of a single character
|
|
86
|
+
* CJK, fullwidth, and emoji characters take 2 columns
|
|
87
|
+
*/
|
|
88
|
+
export declare function charWidth(char: string): number;
|
|
89
|
+
/**
|
|
90
|
+
* Get terminal display width of a string (excluding ANSI codes)
|
|
91
|
+
*/
|
|
92
|
+
export declare function stringWidth(str: string): number;
|
|
84
93
|
/**
|
|
85
94
|
* Strip ANSI codes from string (for length calculation)
|
|
86
95
|
*/
|
package/dist/renderer/ansi.js
CHANGED
|
@@ -101,6 +101,84 @@ export function styled(text, ...styles) {
|
|
|
101
101
|
return text;
|
|
102
102
|
return styles.join('') + text + style.reset;
|
|
103
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Get terminal display width of a single character
|
|
106
|
+
* CJK, fullwidth, and emoji characters take 2 columns
|
|
107
|
+
*/
|
|
108
|
+
export function charWidth(char) {
|
|
109
|
+
const code = char.codePointAt(0);
|
|
110
|
+
if (code === undefined)
|
|
111
|
+
return 0;
|
|
112
|
+
// Control characters
|
|
113
|
+
if (code < 32 || (code >= 0x7f && code < 0xa0))
|
|
114
|
+
return 0;
|
|
115
|
+
// CJK Unified Ideographs
|
|
116
|
+
if (code >= 0x4e00 && code <= 0x9fff)
|
|
117
|
+
return 2;
|
|
118
|
+
// CJK Unified Ideographs Extension A
|
|
119
|
+
if (code >= 0x3400 && code <= 0x4dbf)
|
|
120
|
+
return 2;
|
|
121
|
+
// CJK Unified Ideographs Extension B
|
|
122
|
+
if (code >= 0x20000 && code <= 0x2a6df)
|
|
123
|
+
return 2;
|
|
124
|
+
// CJK Compatibility Ideographs
|
|
125
|
+
if (code >= 0xf900 && code <= 0xfaff)
|
|
126
|
+
return 2;
|
|
127
|
+
// CJK Radicals / Kangxi Radicals
|
|
128
|
+
if (code >= 0x2e80 && code <= 0x2fdf)
|
|
129
|
+
return 2;
|
|
130
|
+
// CJK Strokes / Enclosed CJK
|
|
131
|
+
if (code >= 0x31c0 && code <= 0x33ff)
|
|
132
|
+
return 2;
|
|
133
|
+
// CJK Symbols and Punctuation
|
|
134
|
+
if (code >= 0x3000 && code <= 0x303f)
|
|
135
|
+
return 2;
|
|
136
|
+
// Hiragana, Katakana
|
|
137
|
+
if (code >= 0x3040 && code <= 0x30ff)
|
|
138
|
+
return 2;
|
|
139
|
+
// Katakana Phonetic Extensions
|
|
140
|
+
if (code >= 0x31f0 && code <= 0x31ff)
|
|
141
|
+
return 2;
|
|
142
|
+
// Hangul Jamo
|
|
143
|
+
if (code >= 0x1100 && code <= 0x11ff)
|
|
144
|
+
return 2;
|
|
145
|
+
// Hangul Syllables
|
|
146
|
+
if (code >= 0xac00 && code <= 0xd7af)
|
|
147
|
+
return 2;
|
|
148
|
+
// Hangul Jamo Extended-A/B
|
|
149
|
+
if (code >= 0xa960 && code <= 0xa97f)
|
|
150
|
+
return 2;
|
|
151
|
+
if (code >= 0xd7b0 && code <= 0xd7ff)
|
|
152
|
+
return 2;
|
|
153
|
+
// Fullwidth Forms
|
|
154
|
+
if (code >= 0xff01 && code <= 0xff60)
|
|
155
|
+
return 2;
|
|
156
|
+
if (code >= 0xffe0 && code <= 0xffe6)
|
|
157
|
+
return 2;
|
|
158
|
+
// Bopomofo
|
|
159
|
+
if (code >= 0x3100 && code <= 0x312f)
|
|
160
|
+
return 2;
|
|
161
|
+
// Emoji ranges (common)
|
|
162
|
+
if (code >= 0x1f300 && code <= 0x1f9ff)
|
|
163
|
+
return 2;
|
|
164
|
+
if (code >= 0x1fa00 && code <= 0x1fa6f)
|
|
165
|
+
return 2;
|
|
166
|
+
if (code >= 0x1fa70 && code <= 0x1faff)
|
|
167
|
+
return 2;
|
|
168
|
+
if (code >= 0x2600 && code <= 0x27bf)
|
|
169
|
+
return 2;
|
|
170
|
+
return 1;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Get terminal display width of a string (excluding ANSI codes)
|
|
174
|
+
*/
|
|
175
|
+
export function stringWidth(str) {
|
|
176
|
+
let width = 0;
|
|
177
|
+
for (const char of str) {
|
|
178
|
+
width += charWidth(char);
|
|
179
|
+
}
|
|
180
|
+
return width;
|
|
181
|
+
}
|
|
104
182
|
/**
|
|
105
183
|
* Strip ANSI codes from string (for length calculation)
|
|
106
184
|
*/
|
|
@@ -112,14 +190,14 @@ export function stripAnsi(str) {
|
|
|
112
190
|
* Get visible length of string (excluding ANSI codes)
|
|
113
191
|
*/
|
|
114
192
|
export function visibleLength(str) {
|
|
115
|
-
return stripAnsi(str)
|
|
193
|
+
return stringWidth(stripAnsi(str));
|
|
116
194
|
}
|
|
117
195
|
/**
|
|
118
196
|
* Truncate string to visible length, preserving ANSI codes
|
|
119
197
|
*/
|
|
120
198
|
export function truncate(str, maxLength, suffix = '...') {
|
|
121
199
|
const visible = stripAnsi(str);
|
|
122
|
-
if (visible
|
|
200
|
+
if (stringWidth(visible) <= maxLength)
|
|
123
201
|
return str;
|
|
124
202
|
// Simple truncation - may cut ANSI codes
|
|
125
203
|
// For proper handling, we'd need to parse ANSI sequences
|
|
@@ -138,13 +216,11 @@ export function truncate(str, maxLength, suffix = '...') {
|
|
|
138
216
|
}
|
|
139
217
|
}
|
|
140
218
|
else {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
visibleCount++;
|
|
144
|
-
}
|
|
145
|
-
else {
|
|
219
|
+
const w = charWidth(char);
|
|
220
|
+
if (visibleCount + w > maxLength - suffix.length)
|
|
146
221
|
break;
|
|
147
|
-
|
|
222
|
+
result += char;
|
|
223
|
+
visibleCount += w;
|
|
148
224
|
}
|
|
149
225
|
}
|
|
150
226
|
return result + style.reset + suffix;
|
package/dist/renderer/index.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export { cursor, screen, fg, bg, style, styled, stripAnsi, visibleLength, trunca
|
|
|
9
9
|
export { Screen, Cell } from './Screen';
|
|
10
10
|
export { Input, LineEditor, KeyEvent, KeyHandler } from './Input';
|
|
11
11
|
export { ChatUI, ChatMessage, ChatUIOptions } from './ChatUI';
|
|
12
|
-
export { App,
|
|
12
|
+
export { App, AppOptions, Message } from './App';
|
|
13
13
|
export { createBox, centerBox, BoxStyle, BoxOptions } from './components/Box';
|
|
14
14
|
export { renderModal, renderHelpModal, renderListModal, ModalOptions } from './components/Modal';
|
|
15
15
|
export { renderHelpScreen, helpCategories, keyboardShortcuts } from './components/Help';
|
package/dist/renderer/main.js
CHANGED
|
@@ -620,6 +620,13 @@ function handleCommand(command, args) {
|
|
|
620
620
|
return;
|
|
621
621
|
}
|
|
622
622
|
const newName = args.join('-');
|
|
623
|
+
// Save current session first so there's a file to rename
|
|
624
|
+
const messages = app.getMessages();
|
|
625
|
+
if (messages.length === 0) {
|
|
626
|
+
app.notify('No messages to save. Start a conversation first.');
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
saveSession(sessionId, messages, projectPath);
|
|
623
630
|
if (renameSession(sessionId, newName, projectPath)) {
|
|
624
631
|
sessionId = newName;
|
|
625
632
|
app.notify(`Session renamed to: ${newName}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeep",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.21",
|
|
4
4
|
"description": "AI-powered coding assistant built for the terminal. Multiple LLM providers, project-aware context, and a seamless development workflow.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|