Skip to content

Commit 0c5b339

Browse files
authored
Merge pull request #170 from rajbos/copilot/update-jsonl-file-viewer
Add formatted JSONL viewer to diagnostics
2 parents af7ec09 + 3f79255 commit 0c5b339

File tree

2 files changed

+85
-2
lines changed

2 files changed

+85
-2
lines changed

src/extension.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3185,6 +3185,65 @@ class CopilotTokenTracker implements vscode.Disposable {
31853185
});
31863186
}
31873187

3188+
/**
3189+
* Opens a JSONL file in a formatted view with array brackets and commas.
3190+
* Does not modify the original file.
3191+
*/
3192+
public async showFormattedJsonlFile(sessionFilePath: string): Promise<void> {
3193+
try {
3194+
// Read the file content
3195+
const fileContent = await fs.promises.readFile(sessionFilePath, 'utf-8');
3196+
3197+
// Parse JSONL into array of objects
3198+
const lines = fileContent.trim().split('\n').filter(line => line.trim().length > 0);
3199+
const jsonObjects: unknown[] = [];
3200+
3201+
for (let i = 0; i < lines.length; i++) {
3202+
try {
3203+
const obj = JSON.parse(lines[i]);
3204+
jsonObjects.push(obj);
3205+
} catch (e) {
3206+
// Skip malformed lines with detailed warning
3207+
this.warn(`Skipping malformed line ${i + 1} in ${sessionFilePath}: ${e}`);
3208+
}
3209+
}
3210+
3211+
// Format as JSON array
3212+
const formattedJson = JSON.stringify(jsonObjects, null, 2);
3213+
3214+
// Create an untitled document with the formatted content
3215+
const fileName = path.basename(sessionFilePath, path.extname(sessionFilePath));
3216+
const prettyUri = vscode.Uri.parse(`untitled:${fileName}-formatted.json`);
3217+
3218+
// Check if this document is already open and close it to refresh
3219+
const openDoc = vscode.workspace.textDocuments.find(d => d.uri.toString() === prettyUri.toString());
3220+
if (openDoc) {
3221+
// Close the existing document so we can create a fresh one with updated content
3222+
const editor = vscode.window.visibleTextEditors.find(e => e.document === openDoc);
3223+
if (editor) {
3224+
await vscode.window.showTextDocument(openDoc, editor.viewColumn);
3225+
await vscode.commands.executeCommand('workbench.action.closeActiveEditor');
3226+
}
3227+
}
3228+
3229+
// Create and open the document
3230+
const doc = await vscode.workspace.openTextDocument(prettyUri);
3231+
const editor = await vscode.window.showTextDocument(doc, { preview: true });
3232+
3233+
// Insert the formatted JSON
3234+
await editor.edit((editBuilder) => {
3235+
editBuilder.insert(new vscode.Position(0, 0), formattedJson);
3236+
});
3237+
3238+
// Set language mode to JSON for syntax highlighting
3239+
await vscode.languages.setTextDocumentLanguage(doc, 'json');
3240+
3241+
} catch (error) {
3242+
this.error(`Error formatting JSONL file ${sessionFilePath}:`, error);
3243+
throw error;
3244+
}
3245+
}
3246+
31883247
private getLogViewerHtml(webview: vscode.Webview, logData: SessionLogData): string {
31893248
const nonce = this.getNonce();
31903249
const scriptUri = webview.asWebviewUri(vscode.Uri.joinPath(this.extensionUri, 'dist', 'webview', 'logviewer.js'));
@@ -3577,6 +3636,17 @@ class CopilotTokenTracker implements vscode.Disposable {
35773636
}
35783637
break;
35793638

3639+
case 'openFormattedJsonlFile':
3640+
if (message.file) {
3641+
try {
3642+
await this.showFormattedJsonlFile(message.file);
3643+
} catch (err) {
3644+
const errorMsg = err instanceof Error ? err.message : String(err);
3645+
vscode.window.showErrorMessage('Could not open formatted file: ' + message.file + ' (' + errorMsg + ')');
3646+
}
3647+
}
3648+
break;
3649+
35803650
case 'revealPath':
35813651
if (message.path) {
35823652
try {

src/webview/diagnostics/main.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,7 @@ function renderSessionTable(detailedFiles: SessionFileDetails[], isLoading: bool
303303
<th>Context Refs</th>
304304
<th class="sortable" data-sort="firstInteraction">First Interaction${getSortIndicator('firstInteraction')}</th>
305305
<th class="sortable" data-sort="lastInteraction">Last Interaction${getSortIndicator('lastInteraction')}</th>
306+
<th>Actions</th>
306307
</tr>
307308
</thead>
308309
<tbody>
@@ -318,6 +319,9 @@ function renderSessionTable(detailedFiles: SessionFileDetails[], isLoading: bool
318319
<td title="${getContextRefsSummary(sf.contextReferences)}">${sanitizeNumber(getTotalContextRefs(sf.contextReferences))}</td>
319320
<td>${formatDate(sf.firstInteraction)}</td>
320321
<td>${formatDate(sf.lastInteraction)}</td>
322+
<td>
323+
<a href="#" class="view-formatted-link" data-file="${encodeURIComponent(sf.file)}" title="View formatted JSONL file">📄 View</a>
324+
</td>
321325
</tr>
322326
`).join('')}
323327
</tbody>
@@ -739,8 +743,8 @@ function renderLayout(data: DiagnosticsData): void {
739743
color: #9aa0a6;
740744
margin-top: 4px;
741745
}
742-
.session-file-link, .reveal-link { color: #4FC3F7; text-decoration: underline; cursor: pointer; }
743-
.session-file-link:hover, .reveal-link:hover { color: #81D4FA; }
746+
.session-file-link, .reveal-link, .view-formatted-link { color: #4FC3F7; text-decoration: underline; cursor: pointer; }
747+
.session-file-link:hover, .reveal-link:hover, .view-formatted-link:hover { color: #81D4FA; }
744748
.empty-session-link { color: #999; }
745749
.empty-session-link:hover { color: #aaa; }
746750
.button-group { display: flex; gap: 12px; margin-top: 16px; flex-wrap: wrap; }
@@ -1203,6 +1207,15 @@ function setupStorageLinkHandlers(): void {
12031207
});
12041208
});
12051209

1210+
// View formatted JSONL link handlers
1211+
document.querySelectorAll('.view-formatted-link').forEach(link => {
1212+
link.addEventListener('click', (e) => {
1213+
e.preventDefault();
1214+
const file = decodeURIComponent((link as HTMLElement).getAttribute('data-file') || '');
1215+
vscode.postMessage({ command: 'openFormattedJsonlFile', file });
1216+
});
1217+
});
1218+
12061219
// Reveal link handlers
12071220
document.querySelectorAll('.reveal-link').forEach(link => {
12081221
link.addEventListener('click', (e) => {

0 commit comments

Comments
 (0)