codex-lens 0.1.18 → 0.1.20
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-Bno1eqbI.js → main-CtA0UO3o.js} +32 -32
- package/dist/public/index.html +2 -2
- package/package.json +1 -1
- package/src/aggregator.js +20 -1
- package/src/components/App.jsx +48 -1
- package/src/components/CodeViewer.jsx +48 -10
- 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-CtA0UO3o.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(() => {
|
|
@@ -66,9 +67,50 @@ export function App() {
|
|
|
66
67
|
} else {
|
|
67
68
|
switchToNextTab();
|
|
68
69
|
}
|
|
70
|
+
} else if (e.ctrlKey && e.key === 's') {
|
|
71
|
+
e.preventDefault();
|
|
72
|
+
saveCurrentFile();
|
|
69
73
|
}
|
|
70
74
|
}
|
|
71
75
|
|
|
76
|
+
async function saveCurrentFile() {
|
|
77
|
+
const activeTab = tabs.find(t => t.id === activeTabId);
|
|
78
|
+
if (!activeTab || activeTab.isDiff) return;
|
|
79
|
+
|
|
80
|
+
setSaving(true);
|
|
81
|
+
try {
|
|
82
|
+
const port = window.location.port === '5173' ? '5174' : window.location.port;
|
|
83
|
+
const protocol = window.location.protocol === 'https:' ? 'https:' : 'http:';
|
|
84
|
+
const response = await fetch(`${protocol}//${window.location.hostname}:${port}/api/save-file`, {
|
|
85
|
+
method: 'POST',
|
|
86
|
+
headers: { 'Content-Type': 'application/json' },
|
|
87
|
+
body: JSON.stringify({ path: activeTab.path, content: activeTab.content })
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
if (response.ok) {
|
|
91
|
+
setTabs(prevTabs => prevTabs.map(t =>
|
|
92
|
+
t.id === activeTabId ? { ...t, isModified: false } : t
|
|
93
|
+
));
|
|
94
|
+
} else {
|
|
95
|
+
const error = await response.json();
|
|
96
|
+
console.error('Failed to save file:', error);
|
|
97
|
+
}
|
|
98
|
+
} catch (error) {
|
|
99
|
+
console.error('Failed to save file:', error);
|
|
100
|
+
} finally {
|
|
101
|
+
setSaving(false);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function handleContentChange(newContent) {
|
|
106
|
+
setTabs(prevTabs => prevTabs.map(t => {
|
|
107
|
+
if (t.id === activeTabId) {
|
|
108
|
+
return { ...t, content: newContent, isModified: true };
|
|
109
|
+
}
|
|
110
|
+
return t;
|
|
111
|
+
}));
|
|
112
|
+
}
|
|
113
|
+
|
|
72
114
|
function connectWebSocket() {
|
|
73
115
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
74
116
|
const host = window.location.hostname;
|
|
@@ -282,6 +324,8 @@ export function App() {
|
|
|
282
324
|
diff={activeTab.diff}
|
|
283
325
|
isDiff={activeTab.isDiff}
|
|
284
326
|
filePath={activeTab.path}
|
|
327
|
+
onChange={handleContentChange}
|
|
328
|
+
isModified={activeTab.isModified}
|
|
285
329
|
/>
|
|
286
330
|
)}
|
|
287
331
|
</div>
|
|
@@ -325,7 +369,10 @@ function TabBar({ tabs, activeTabId, onTabClick, onTabClose, onContextMenu }) {
|
|
|
325
369
|
onClick={() => onTabClick(tab.id)}
|
|
326
370
|
onContextMenu={(e) => onContextMenu(e, tab.id)}
|
|
327
371
|
>
|
|
328
|
-
<span className="tab-name">
|
|
372
|
+
<span className="tab-name">
|
|
373
|
+
{tab.isModified && <span className="tab-modified">*</span>}
|
|
374
|
+
{tab.name}
|
|
375
|
+
</span>
|
|
329
376
|
<button
|
|
330
377
|
className="tab-close"
|
|
331
378
|
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';
|
|
@@ -67,6 +67,14 @@ const removedLineDecoration = Decoration.line({
|
|
|
67
67
|
attributes: { 'data-diff-type': 'removed' }
|
|
68
68
|
});
|
|
69
69
|
|
|
70
|
+
const addedMarkDecoration = Decoration.mark({
|
|
71
|
+
class: 'cm-diff-added-mark'
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const removedMarkDecoration = Decoration.mark({
|
|
75
|
+
class: 'cm-diff-removed-mark'
|
|
76
|
+
});
|
|
77
|
+
|
|
70
78
|
function createDiffHighlightPlugin(diffData) {
|
|
71
79
|
return ViewPlugin.fromClass(class {
|
|
72
80
|
decorations;
|
|
@@ -96,8 +104,14 @@ function createDiffHighlightPlugin(diffData) {
|
|
|
96
104
|
if (diffLine) {
|
|
97
105
|
if (diffLine.added) {
|
|
98
106
|
builder.add(line.from, line.from, addedLineDecoration);
|
|
107
|
+
if (line.length > 0) {
|
|
108
|
+
builder.add(line.from, line.from + 1, addedMarkDecoration);
|
|
109
|
+
}
|
|
99
110
|
} else if (diffLine.removed) {
|
|
100
111
|
builder.add(line.from, line.from, removedLineDecoration);
|
|
112
|
+
if (line.length > 0) {
|
|
113
|
+
builder.add(line.from, line.from + 1, removedMarkDecoration);
|
|
114
|
+
}
|
|
101
115
|
}
|
|
102
116
|
}
|
|
103
117
|
}
|
|
@@ -167,17 +181,17 @@ const darkTheme = EditorView.theme({
|
|
|
167
181
|
},
|
|
168
182
|
'.cm-diff-added-line': {
|
|
169
183
|
backgroundColor: 'rgba(34, 197, 94, 0.15)',
|
|
170
|
-
borderLeft: '3px solid #22c55e',
|
|
171
184
|
},
|
|
172
|
-
'.cm-diff-added-
|
|
185
|
+
'.cm-diff-added-mark': {
|
|
173
186
|
color: '#4ade80',
|
|
187
|
+
fontWeight: 'bold',
|
|
174
188
|
},
|
|
175
189
|
'.cm-diff-removed-line': {
|
|
176
190
|
backgroundColor: 'rgba(239, 68, 68, 0.15)',
|
|
177
|
-
borderLeft: '3px solid #ef4444',
|
|
178
191
|
},
|
|
179
|
-
'.cm-diff-removed-
|
|
192
|
+
'.cm-diff-removed-mark': {
|
|
180
193
|
color: '#f87171',
|
|
194
|
+
fontWeight: 'bold',
|
|
181
195
|
},
|
|
182
196
|
}, { dark: true });
|
|
183
197
|
|
|
@@ -201,8 +215,11 @@ const darkHighlightStyle = HighlightStyle.define([
|
|
|
201
215
|
|
|
202
216
|
const syntaxTheme = syntaxHighlighting(darkHighlightStyle);
|
|
203
217
|
|
|
204
|
-
export function CodeViewer({ content, diff, isDiff, filePath }) {
|
|
218
|
+
export function CodeViewer({ content, diff, isDiff, filePath, onChange, isModified }) {
|
|
205
219
|
const editorRef = useRef(null);
|
|
220
|
+
const [localContent, setLocalContent] = useState(content || '');
|
|
221
|
+
|
|
222
|
+
const isEditable = !isDiff;
|
|
206
223
|
|
|
207
224
|
const extensions = useMemo(() => {
|
|
208
225
|
const exts = [
|
|
@@ -228,19 +245,39 @@ export function CodeViewer({ content, diff, isDiff, filePath }) {
|
|
|
228
245
|
|
|
229
246
|
const code = useMemo(() => {
|
|
230
247
|
if (isDiff && diff) {
|
|
231
|
-
return diff.map(line =>
|
|
248
|
+
return diff.map(line => {
|
|
249
|
+
if (line.added) {
|
|
250
|
+
return '+' + line.content;
|
|
251
|
+
} else if (line.removed) {
|
|
252
|
+
return '-' + line.content;
|
|
253
|
+
}
|
|
254
|
+
return ' ' + line.content;
|
|
255
|
+
}).join('\n');
|
|
232
256
|
}
|
|
233
257
|
return content || '';
|
|
234
258
|
}, [content, diff, isDiff]);
|
|
235
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
|
+
|
|
236
273
|
return (
|
|
237
274
|
<div className="code-viewer-codemirror">
|
|
238
275
|
<CodeMirror
|
|
239
|
-
value={code}
|
|
276
|
+
value={isDiff ? code : localContent}
|
|
240
277
|
height="100%"
|
|
241
278
|
theme={darkTheme}
|
|
242
279
|
extensions={extensions}
|
|
243
|
-
editable={
|
|
280
|
+
editable={isEditable}
|
|
244
281
|
basicSetup={{
|
|
245
282
|
lineNumbers: true,
|
|
246
283
|
foldGutter: false,
|
|
@@ -248,6 +285,7 @@ export function CodeViewer({ content, diff, isDiff, filePath }) {
|
|
|
248
285
|
highlightSelectionMatches: false,
|
|
249
286
|
bracketMatching: true,
|
|
250
287
|
}}
|
|
288
|
+
onChange={isEditable ? handleChange : undefined}
|
|
251
289
|
onCreateEditor={(view) => {
|
|
252
290
|
editorRef.current = view;
|
|
253
291
|
}}
|