Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3185,6 +3185,65 @@ class CopilotTokenTracker implements vscode.Disposable {
});
}

/**
* Opens a JSONL file in a formatted view with array brackets and commas.
* Does not modify the original file.
*/
public async showFormattedJsonlFile(sessionFilePath: string): Promise<void> {
try {
// Read the file content
const fileContent = await fs.promises.readFile(sessionFilePath, 'utf-8');

// Parse JSONL into array of objects
const lines = fileContent.trim().split('\n').filter(line => line.trim().length > 0);
const jsonObjects: unknown[] = [];

for (let i = 0; i < lines.length; i++) {
try {
const obj = JSON.parse(lines[i]);
jsonObjects.push(obj);
} catch (e) {
// Skip malformed lines with detailed warning
this.warn(`Skipping malformed line ${i + 1} in ${sessionFilePath}: ${e}`);
}
}

// Format as JSON array
const formattedJson = JSON.stringify(jsonObjects, null, 2);

// Create an untitled document with the formatted content
const fileName = path.basename(sessionFilePath, path.extname(sessionFilePath));
const prettyUri = vscode.Uri.parse(`untitled:${fileName}-formatted.json`);

// Check if this document is already open and close it to refresh
const openDoc = vscode.workspace.textDocuments.find(d => d.uri.toString() === prettyUri.toString());
if (openDoc) {
// Close the existing document so we can create a fresh one with updated content
const editor = vscode.window.visibleTextEditors.find(e => e.document === openDoc);
if (editor) {
await vscode.window.showTextDocument(openDoc, editor.viewColumn);
await vscode.commands.executeCommand('workbench.action.closeActiveEditor');
}
}

// Create and open the document
const doc = await vscode.workspace.openTextDocument(prettyUri);
const editor = await vscode.window.showTextDocument(doc, { preview: true });

// Insert the formatted JSON
await editor.edit((editBuilder) => {
editBuilder.insert(new vscode.Position(0, 0), formattedJson);
});

// Set language mode to JSON for syntax highlighting
await vscode.languages.setTextDocumentLanguage(doc, 'json');

} catch (error) {
this.error(`Error formatting JSONL file ${sessionFilePath}:`, error);
throw error;
}
}

private getLogViewerHtml(webview: vscode.Webview, logData: SessionLogData): string {
const nonce = this.getNonce();
const scriptUri = webview.asWebviewUri(vscode.Uri.joinPath(this.extensionUri, 'dist', 'webview', 'logviewer.js'));
Expand Down Expand Up @@ -3577,6 +3636,17 @@ class CopilotTokenTracker implements vscode.Disposable {
}
break;

case 'openFormattedJsonlFile':
if (message.file) {
try {
await this.showFormattedJsonlFile(message.file);
} catch (err) {
const errorMsg = err instanceof Error ? err.message : String(err);
vscode.window.showErrorMessage('Could not open formatted file: ' + message.file + ' (' + errorMsg + ')');
}
}
break;

case 'revealPath':
if (message.path) {
try {
Expand Down
17 changes: 15 additions & 2 deletions src/webview/diagnostics/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@ function renderSessionTable(detailedFiles: SessionFileDetails[], isLoading: bool
<th>Context Refs</th>
<th class="sortable" data-sort="firstInteraction">First Interaction${getSortIndicator('firstInteraction')}</th>
<th class="sortable" data-sort="lastInteraction">Last Interaction${getSortIndicator('lastInteraction')}</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
Expand All @@ -318,6 +319,9 @@ function renderSessionTable(detailedFiles: SessionFileDetails[], isLoading: bool
<td title="${getContextRefsSummary(sf.contextReferences)}">${sanitizeNumber(getTotalContextRefs(sf.contextReferences))}</td>
<td>${formatDate(sf.firstInteraction)}</td>
<td>${formatDate(sf.lastInteraction)}</td>
<td>
<a href="#" class="view-formatted-link" data-file="${encodeURIComponent(sf.file)}" title="View formatted JSONL file">📄 View</a>
</td>
</tr>
`).join('')}
</tbody>
Expand Down Expand Up @@ -739,8 +743,8 @@ function renderLayout(data: DiagnosticsData): void {
color: #9aa0a6;
margin-top: 4px;
}
.session-file-link, .reveal-link { color: #4FC3F7; text-decoration: underline; cursor: pointer; }
.session-file-link:hover, .reveal-link:hover { color: #81D4FA; }
.session-file-link, .reveal-link, .view-formatted-link { color: #4FC3F7; text-decoration: underline; cursor: pointer; }
.session-file-link:hover, .reveal-link:hover, .view-formatted-link:hover { color: #81D4FA; }
.empty-session-link { color: #999; }
.empty-session-link:hover { color: #aaa; }
.button-group { display: flex; gap: 12px; margin-top: 16px; flex-wrap: wrap; }
Expand Down Expand Up @@ -1203,6 +1207,15 @@ function setupStorageLinkHandlers(): void {
});
});

// View formatted JSONL link handlers
document.querySelectorAll('.view-formatted-link').forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
const file = decodeURIComponent((link as HTMLElement).getAttribute('data-file') || '');
vscode.postMessage({ command: 'openFormattedJsonlFile', file });
});
});

// Reveal link handlers
document.querySelectorAll('.reveal-link').forEach(link => {
link.addEventListener('click', (e) => {
Expand Down
Loading