codex-lens 0.1.19 → 0.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/aggregator.js +18 -1
- package/dist/public/assets/{main-7-y-Utze.css → main-CYNmzqDG.css} +1 -1
- package/dist/public/assets/{main-CEqXetbU.js → main-Dv8f8BcE.js} +34 -34
- package/dist/public/index.html +2 -2
- package/package.json +1 -1
- package/src/aggregator.js +20 -1
- package/src/components/App.jsx +62 -1
- package/src/components/CodeViewer.jsx +22 -5
- package/src/global.css +6 -0
package/dist/public/index.html
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Codex Lens</title>
|
|
7
|
-
<script type="module" crossorigin src="./assets/main-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="./assets/main-
|
|
7
|
+
<script type="module" crossorigin src="./assets/main-Dv8f8BcE.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="./assets/main-CYNmzqDG.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div id="root"></div>
|
package/package.json
CHANGED
package/src/aggregator.js
CHANGED
|
@@ -9,7 +9,7 @@ import { createLogger } from './lib/logger.js';
|
|
|
9
9
|
import { spawnCodex, writeToPty, resizePty, killPty, onPtyData, onPtyExit, getPtyState, getOutputBuffer } from './pty-manager.js';
|
|
10
10
|
import path from 'path';
|
|
11
11
|
import { fileURLToPath } from 'url';
|
|
12
|
-
import { readFileSync, existsSync } from 'fs';
|
|
12
|
+
import { readFileSync, existsSync, writeFileSync } from 'fs';
|
|
13
13
|
|
|
14
14
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
15
15
|
|
|
@@ -107,6 +107,25 @@ class Aggregator {
|
|
|
107
107
|
res.json({ success: true });
|
|
108
108
|
});
|
|
109
109
|
|
|
110
|
+
app.post('/api/save-file', (req, res) => {
|
|
111
|
+
const { path: filePath, content } = req.body;
|
|
112
|
+
if (!filePath) {
|
|
113
|
+
return res.status(400).json({ error: 'Path is required' });
|
|
114
|
+
}
|
|
115
|
+
if (content === undefined) {
|
|
116
|
+
return res.status(400).json({ error: 'Content is required' });
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
writeFileSync(filePath, content, 'utf-8');
|
|
121
|
+
logger.info(`File saved: ${filePath}`);
|
|
122
|
+
res.json({ success: true, path: filePath });
|
|
123
|
+
} catch (error) {
|
|
124
|
+
logger.error(`Failed to save file: ${filePath} - ${error.message}`);
|
|
125
|
+
res.status(500).json({ error: error.message });
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
110
129
|
await new Promise((resolve) => {
|
|
111
130
|
this.httpServer = createServer(app);
|
|
112
131
|
|
package/src/components/App.jsx
CHANGED
|
@@ -12,6 +12,7 @@ export function App() {
|
|
|
12
12
|
const [latestVersion, setLatestVersion] = useState(null);
|
|
13
13
|
const [hasUpdate, setHasUpdate] = useState(false);
|
|
14
14
|
const [projectName, setProjectName] = useState('');
|
|
15
|
+
const [saving, setSaving] = useState(false);
|
|
15
16
|
const wsRef = useRef(null);
|
|
16
17
|
|
|
17
18
|
useEffect(() => {
|
|
@@ -20,9 +21,18 @@ export function App() {
|
|
|
20
21
|
document.addEventListener('click', handleDocumentClick);
|
|
21
22
|
document.addEventListener('keydown', handleKeyDown);
|
|
22
23
|
|
|
24
|
+
const preventBrowserSave = (e) => {
|
|
25
|
+
if (e.ctrlKey && e.key === 's') {
|
|
26
|
+
e.preventDefault();
|
|
27
|
+
e.stopPropagation();
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
window.addEventListener('keydown', preventBrowserSave, true);
|
|
31
|
+
|
|
23
32
|
return () => {
|
|
24
33
|
document.removeEventListener('click', handleDocumentClick);
|
|
25
34
|
document.removeEventListener('keydown', handleKeyDown);
|
|
35
|
+
window.removeEventListener('keydown', preventBrowserSave, true);
|
|
26
36
|
if (wsRef.current) {
|
|
27
37
|
wsRef.current.close();
|
|
28
38
|
}
|
|
@@ -54,6 +64,14 @@ export function App() {
|
|
|
54
64
|
}
|
|
55
65
|
|
|
56
66
|
function handleKeyDown(e) {
|
|
67
|
+
if (e.ctrlKey && e.key === 's') {
|
|
68
|
+
e.preventDefault();
|
|
69
|
+
if (activeTabId) {
|
|
70
|
+
saveCurrentFile();
|
|
71
|
+
}
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
57
75
|
if (!activeTabId) return;
|
|
58
76
|
|
|
59
77
|
if (e.ctrlKey && e.key === 'w') {
|
|
@@ -69,6 +87,44 @@ export function App() {
|
|
|
69
87
|
}
|
|
70
88
|
}
|
|
71
89
|
|
|
90
|
+
async function saveCurrentFile() {
|
|
91
|
+
const activeTab = tabs.find(t => t.id === activeTabId);
|
|
92
|
+
if (!activeTab || activeTab.isDiff) return;
|
|
93
|
+
|
|
94
|
+
setSaving(true);
|
|
95
|
+
try {
|
|
96
|
+
const port = window.location.port === '5173' ? '5174' : window.location.port;
|
|
97
|
+
const protocol = window.location.protocol === 'https:' ? 'https:' : 'http:';
|
|
98
|
+
const response = await fetch(`${protocol}//${window.location.hostname}:${port}/api/save-file`, {
|
|
99
|
+
method: 'POST',
|
|
100
|
+
headers: { 'Content-Type': 'application/json' },
|
|
101
|
+
body: JSON.stringify({ path: activeTab.path, content: activeTab.content })
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
if (response.ok) {
|
|
105
|
+
setTabs(prevTabs => prevTabs.map(t =>
|
|
106
|
+
t.id === activeTabId ? { ...t, isModified: false } : t
|
|
107
|
+
));
|
|
108
|
+
} else {
|
|
109
|
+
const error = await response.json();
|
|
110
|
+
console.error('Failed to save file:', error);
|
|
111
|
+
}
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.error('Failed to save file:', error);
|
|
114
|
+
} finally {
|
|
115
|
+
setSaving(false);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function handleContentChange(newContent) {
|
|
120
|
+
setTabs(prevTabs => prevTabs.map(t => {
|
|
121
|
+
if (t.id === activeTabId) {
|
|
122
|
+
return { ...t, content: newContent, isModified: true };
|
|
123
|
+
}
|
|
124
|
+
return t;
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
|
|
72
128
|
function connectWebSocket() {
|
|
73
129
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
74
130
|
const host = window.location.hostname;
|
|
@@ -282,6 +338,8 @@ export function App() {
|
|
|
282
338
|
diff={activeTab.diff}
|
|
283
339
|
isDiff={activeTab.isDiff}
|
|
284
340
|
filePath={activeTab.path}
|
|
341
|
+
onChange={handleContentChange}
|
|
342
|
+
isModified={activeTab.isModified}
|
|
285
343
|
/>
|
|
286
344
|
)}
|
|
287
345
|
</div>
|
|
@@ -325,7 +383,10 @@ function TabBar({ tabs, activeTabId, onTabClick, onTabClose, onContextMenu }) {
|
|
|
325
383
|
onClick={() => onTabClick(tab.id)}
|
|
326
384
|
onContextMenu={(e) => onContextMenu(e, tab.id)}
|
|
327
385
|
>
|
|
328
|
-
<span className="tab-name">
|
|
386
|
+
<span className="tab-name">
|
|
387
|
+
{tab.isModified && <span className="tab-modified">*</span>}
|
|
388
|
+
{tab.name}
|
|
389
|
+
</span>
|
|
329
390
|
<button
|
|
330
391
|
className="tab-close"
|
|
331
392
|
onClick={(e) => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import React, { useMemo, useRef } from 'react';
|
|
1
|
+
import React, { useMemo, useRef, useState, useCallback } from 'react';
|
|
2
2
|
import CodeMirror from '@uiw/react-codemirror';
|
|
3
|
-
import { EditorView, Decoration, ViewPlugin,
|
|
3
|
+
import { EditorView, Decoration, ViewPlugin, keymap } from '@codemirror/view';
|
|
4
4
|
import { HighlightStyle, syntaxHighlighting } from '@codemirror/language';
|
|
5
5
|
import { tags as t } from '@lezer/highlight';
|
|
6
6
|
import { RangeSetBuilder } from '@codemirror/state';
|
|
@@ -215,8 +215,11 @@ const darkHighlightStyle = HighlightStyle.define([
|
|
|
215
215
|
|
|
216
216
|
const syntaxTheme = syntaxHighlighting(darkHighlightStyle);
|
|
217
217
|
|
|
218
|
-
export function CodeViewer({ content, diff, isDiff, filePath }) {
|
|
218
|
+
export function CodeViewer({ content, diff, isDiff, filePath, onChange, isModified }) {
|
|
219
219
|
const editorRef = useRef(null);
|
|
220
|
+
const [localContent, setLocalContent] = useState(content || '');
|
|
221
|
+
|
|
222
|
+
const isEditable = !isDiff;
|
|
220
223
|
|
|
221
224
|
const extensions = useMemo(() => {
|
|
222
225
|
const exts = [
|
|
@@ -254,14 +257,27 @@ export function CodeViewer({ content, diff, isDiff, filePath }) {
|
|
|
254
257
|
return content || '';
|
|
255
258
|
}, [content, diff, isDiff]);
|
|
256
259
|
|
|
260
|
+
const handleChange = useCallback((value) => {
|
|
261
|
+
setLocalContent(value);
|
|
262
|
+
if (onChange) {
|
|
263
|
+
onChange(value);
|
|
264
|
+
}
|
|
265
|
+
}, [onChange]);
|
|
266
|
+
|
|
267
|
+
React.useEffect(() => {
|
|
268
|
+
if (!isDiff) {
|
|
269
|
+
setLocalContent(content || '');
|
|
270
|
+
}
|
|
271
|
+
}, [content, isDiff]);
|
|
272
|
+
|
|
257
273
|
return (
|
|
258
274
|
<div className="code-viewer-codemirror">
|
|
259
275
|
<CodeMirror
|
|
260
|
-
value={code}
|
|
276
|
+
value={isDiff ? code : localContent}
|
|
261
277
|
height="100%"
|
|
262
278
|
theme={darkTheme}
|
|
263
279
|
extensions={extensions}
|
|
264
|
-
editable={
|
|
280
|
+
editable={isEditable}
|
|
265
281
|
basicSetup={{
|
|
266
282
|
lineNumbers: true,
|
|
267
283
|
foldGutter: false,
|
|
@@ -269,6 +285,7 @@ export function CodeViewer({ content, diff, isDiff, filePath }) {
|
|
|
269
285
|
highlightSelectionMatches: false,
|
|
270
286
|
bracketMatching: true,
|
|
271
287
|
}}
|
|
288
|
+
onChange={isEditable ? handleChange : undefined}
|
|
272
289
|
onCreateEditor={(view) => {
|
|
273
290
|
editorRef.current = view;
|
|
274
291
|
}}
|