This commit is contained in:
2025-03-09 21:17:32 +01:00
parent 3dbcf00e9f
commit a96903da0e
23 changed files with 891 additions and 227 deletions

View File

@@ -0,0 +1,134 @@
# OurDB Viewer VSCode Extension
A Visual Studio Code extension for viewing OurDB files line by line. This extension provides a simple way to inspect the contents of .ourdb files directly in VSCode.
## Features
- Displays OurDB records with detailed information
- Shows record IDs, sizes, and content
- Formats JSON data for better readability
- Provides file metadata (size, modification date)
- Automatic file format detection for .ourdb files
- Custom editor for binary .ourdb files
- Context menu integration for .ourdb files
- File system watcher for automatic updates
- Refresh command to update the view
## Installation
//TODO: needs to be added to hero cmd line in installers
```
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.develop.vscode_extensions.ourdb
// This example shows how to use the ourdb module to install or uninstall the VSCode extension
// Install the extension
ourdb.install_extension() or {
eprintln('Failed to install extension: ${err}')
exit(1)
}
// To uninstall, uncomment the following lines:
/*
ourdb.uninstall_extension() or {
eprintln('Failed to uninstall extension: ${err}')
exit(1)
}
```
3. Restart VSCode
### Manual Installation
If the scripts don't work, you can install manually:
1. Copy this extension folder to your VSCode extensions directory:
- Windows: `%USERPROFILE%\.vscode\extensions\local-herolib.ourdb-viewer-0.0.1`
- macOS: `~/.vscode/extensions/local-herolib.ourdb-viewer-0.0.1`
- Linux: `~/.vscode/extensions/local-herolib.ourdb-viewer-0.0.1`
2. Restart VSCode
3. Verify installation:
- Open the Command Palette (Ctrl+Shift+P or Cmd+Shift+P)
- Type "Extensions: Show Installed Extensions"
- You should see "OurDB Viewer" in the list
## Usage
1. Open any .ourdb file in VSCode
- The extension will automatically detect .ourdb files and open them in the custom editor
- If a file is detected as binary, you can right-click it in the Explorer and select "OurDB: Open File"
2. View the formatted contents:
- File metadata (path, size, modification date)
- Each record with its ID, size, and content
- JSON data is automatically formatted for better readability
3. Update the view:
- The view automatically updates when the file changes
- Use the "OurDB: Refresh View" command from the command palette to manually refresh
## Troubleshooting
If the extension doesn't activate when opening .ourdb files:
1. Check that the extension is properly installed (see verification step above)
2. Try running the "Developer: Reload Window" command
3. Check the Output panel (View > Output) and select "OurDB Viewer" from the dropdown to see logs and error messages
### Viewing Extension Logs
The extension creates a dedicated output channel for logging:
1. Open the Output panel in VSCode (View > Output)
2. Select "OurDB Viewer" from the dropdown menu at the top right of the Output panel
3. You'll see detailed logs about the extension's activity, including file processing and any errors
If you don't see "OurDB Viewer" in the dropdown, try:
- Restarting VSCode
- Opening an .ourdb file (which should activate the extension)
- Reinstalling the extension using the provided installation scripts
### Working with Binary Files
If VSCode detects your .ourdb file as binary and doesn't automatically open it with the OurDB Viewer:
1. Right-click the file in the Explorer panel
2. Select "OurDB: Open File" from the context menu
3. The file will open in the custom OurDB Viewer
The extension now includes a custom editor that can handle binary .ourdb files directly, without needing to convert them to text first.
## File Format
This extension reads OurDB files according to the following format:
- 2 bytes: Data size (little-endian)
- 4 bytes: CRC32 checksum
- 6 bytes: Previous record location
- N bytes: Actual data
## Development
To modify or enhance this extension:
1. Make your changes to `extension.js` or `package.json`
2. Test by running the extension in a new VSCode window:
- Press F5 in VSCode with this extension folder open
- This will launch a new "Extension Development Host" window
- Open an .ourdb file in the development window to test
3. Package using `vsce package` if you want to create a VSIX file:
```
npm install -g @vscode/vsce
vsce package
```
## License
This extension is part of the HeroLib project and follows its licensing terms.

View File

@@ -0,0 +1,114 @@
module ourdb
import os
// Embed the extension files directly into the binary
#embed_file extension_js_content := 'extension.js'
#embed_file package_json_content := 'package.json'
#embed_file readme_content := 'README.md'
// VSCodeExtension represents the OurDB VSCode extension
pub struct VSCodeExtension {
pub mut:
extension_dir string
}
// new creates a new VSCodeExtension instance
pub fn new() !VSCodeExtension {
// Determine the extension directory based on OS
extension_dir := get_extension_dir()
return VSCodeExtension{
extension_dir: extension_dir
}
}
// get_extension_dir determines the VSCode extension directory based on OS
fn get_extension_dir() string {
home_dir := os.home_dir()
// Extension directory path based on OS
return os.join_path(home_dir, '.vscode', 'extensions', 'local-herolib.ourdb-viewer-0.0.1')
}
// install installs the OurDB VSCode extension
pub fn (mut ext VSCodeExtension) install() ! {
// Check if already installed
if ext.is_installed() {
println('OurDB VSCode extension is already installed at: ${ext.extension_dir}')
println('To reinstall, first uninstall using the uninstall() function')
return
}
// Create extension directory if it doesn't exist
os.mkdir_all(ext.extension_dir) or {
return error('Failed to create extension directory: ${err}')
}
// Write embedded files to the extension directory
// extension.js
os.write_file(os.join_path(ext.extension_dir, 'extension.js'), extension_js_content.to_string()) or {
return error('Failed to write extension.js: ${err}')
}
// package.json
os.write_file(os.join_path(ext.extension_dir, 'package.json'), package_json_content.to_string()) or {
return error('Failed to write package.json: ${err}')
}
// README.md
os.write_file(os.join_path(ext.extension_dir, 'README.md'), readme_content.to_string()) or {
return error('Failed to write README.md: ${err}')
}
println('OurDB Viewer extension installed to: ${ext.extension_dir}')
println('Please restart VSCode for the changes to take effect.')
println('After restarting, you should be able to open .ourdb files.')
return
}
// uninstall removes the OurDB VSCode extension
pub fn (mut ext VSCodeExtension) uninstall() ! {
if os.exists(ext.extension_dir) {
os.rmdir_all(ext.extension_dir) or {
return error('Failed to remove extension directory: ${err}')
}
println('OurDB Viewer extension uninstalled from: ${ext.extension_dir}')
println('Please restart VSCode for the changes to take effect.')
} else {
println('Extension not found at: ${ext.extension_dir}')
}
return
}
// is_installed checks if the extension is installed
pub fn (ext VSCodeExtension) is_installed() bool {
return os.exists(ext.extension_dir) &&
os.exists(os.join_path(ext.extension_dir, 'extension.js')) &&
os.exists(os.join_path(ext.extension_dir, 'package.json'))
}
// install_extension is a convenience function to install the extension
pub fn install_extension() ! {
mut ext := new() or {
return error('Failed to initialize extension: ${err}')
}
ext.install() or {
return error('Failed to install extension: ${err}')
}
}
// uninstall_extension is a convenience function to uninstall the extension
pub fn uninstall_extension() ! {
mut ext := new() or {
return error('Failed to initialize extension: ${err}')
}
ext.uninstall() or {
return error('Failed to uninstall extension: ${err}')
}
}

View File

@@ -0,0 +1,359 @@
const vscode = require('vscode');
const fs = require('fs');
/**
* Reads an OurDB record from a buffer at the given offset
* @param {Buffer} buffer The file buffer
* @param {number} offset The offset to read from
* @returns {Object} The record data and next offset
*/
function readRecord(buffer, offset) {
// Record format:
// - 2 bytes: Data size (little-endian)
// - 4 bytes: CRC32 checksum
// - 6 bytes: Previous record location
// - N bytes: Actual data
// Read data size (first 2 bytes) in little-endian format
const dataSize = buffer[offset] | (buffer[offset + 1] << 8);
if (dataSize === 0 || offset + 12 + dataSize > buffer.length) {
throw new Error('Invalid record or end of file');
}
// Extract the data portion
const data = buffer.slice(offset + 12, offset + 12 + dataSize);
return {
size: dataSize,
data: data.toString(), // Assuming UTF-8 data
nextOffset: offset + 12 + dataSize
};
}
/**
* Parse an OurDB file and return its contents as formatted text
* @param {string} filePath Path to the OurDB file
* @returns {string} Formatted content
*/
function parseOurDBFile(filePath) {
try {
// Check if file exists
if (!fs.existsSync(filePath)) {
return `Error: File not found at ${filePath}`;
}
// Get file stats
const stats = fs.statSync(filePath);
if (stats.size === 0) {
return 'Error: File is empty';
}
const buffer = fs.readFileSync(filePath);
let content = [];
content.push(`# OurDB File: ${filePath}`);
content.push(`# File size: ${stats.size} bytes`);
content.push(`# Last modified: ${stats.mtime.toLocaleString()}`);
content.push('');
let offset = 0;
let id = 1;
let recordsRead = 0;
let errorCount = 0;
// Read records until we reach the end of file
while (offset < buffer.length) {
try {
const record = readRecord(buffer, offset);
// Try to parse as JSON if it looks like JSON
let displayData = record.data;
if (record.data.trim().startsWith('{') || record.data.trim().startsWith('[')) {
try {
const jsonObj = JSON.parse(record.data);
displayData = JSON.stringify(jsonObj, null, 2);
} catch (jsonErr) {
// Not valid JSON, use as-is
}
}
content.push(`Record ${id} (size: ${record.size} bytes):`);
content.push('```');
content.push(displayData);
content.push('```');
content.push('');
offset = record.nextOffset;
id++;
recordsRead++;
} catch (e) {
errorCount++;
if (errorCount === 1) {
// Only show the first error
content.push(`Error reading record at offset ${offset}: ${e.message}`);
}
// Skip ahead to try to find next valid record
offset += 1;
// If we've had too many errors in a row, stop trying
if (errorCount > 10 && recordsRead === 0) {
content.push('Too many errors encountered. This may not be a valid OurDB file.');
break;
}
}
}
if (recordsRead === 0) {
content.push('No valid records found in this file.');
} else {
content.push(`Total records: ${recordsRead}`);
}
return content.join('\n');
} catch (error) {
return `Error reading OurDB file: ${error.message}\n${error.stack}`;
}
}
/**
* Content provider for the ourdb:// scheme
*/
class OurDBContentProvider {
constructor() {
this._onDidChange = new vscode.EventEmitter();
this.onDidChange = this._onDidChange.event;
}
provideTextDocumentContent(uri) {
return parseOurDBFile(uri.fsPath);
}
}
/**
* Custom document for OurDB files
*/
class OurDBDocument {
constructor(uri) {
this.uri = uri;
}
dispose() {
// Nothing to dispose
}
}
/**
* Custom editor provider for .ourdb files
*/
class OurDBEditorProvider {
constructor(outputChannel) {
this.outputChannel = outputChannel;
}
// Required method for custom editors
async openCustomDocument(uri, _openContext, _token) {
this.outputChannel.appendLine(`Opening custom document for: ${uri.fsPath}`);
return new OurDBDocument(uri);
}
// Required method for custom editors
resolveCustomEditor(document, webviewPanel, _token) {
this.outputChannel.appendLine(`Custom editor resolving for: ${document.uri.fsPath}`);
try {
const content = parseOurDBFile(document.uri.fsPath);
// Set the HTML content for the webview
webviewPanel.webview.options = {
enableScripts: false
};
webviewPanel.webview.html = this.getHtmlForWebview(content);
this.outputChannel.appendLine('Custom editor content set');
} catch (error) {
this.outputChannel.appendLine(`Error in resolveCustomEditor: ${error.message}`);
this.outputChannel.appendLine(error.stack);
webviewPanel.webview.html = `<html><body>
<h1>Error</h1>
<pre>${error.message}\n${error.stack}</pre>
</body></html>`;
}
}
getHtmlForWebview(content) {
// Convert the content to HTML with syntax highlighting
const htmlContent = content
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/```([\s\S]*?)```/g, (match, code) => {
return `<pre class="code-block">${code}</pre>`;
})
.replace(/^# (.*?)$/gm, '<h1>$1</h1>')
.replace(/\n/g, '<br>');
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OurDB Viewer</title>
<style>
body {
font-family: var(--vscode-editor-font-family);
font-size: var(--vscode-editor-font-size);
padding: 0 20px;
color: var(--vscode-editor-foreground);
background-color: var(--vscode-editor-background);
}
h1 {
font-size: 1.2em;
margin-top: 20px;
margin-bottom: 10px;
color: var(--vscode-editorLink-activeForeground);
}
.code-block {
background-color: var(--vscode-textCodeBlock-background);
padding: 10px;
border-radius: 3px;
font-family: var(--vscode-editor-font-family);
white-space: pre-wrap;
margin: 10px 0;
}
</style>
</head>
<body>
${htmlContent}
</body>
</html>`;
}
}
function activate(context) {
// Create output channel for logging
const outputChannel = vscode.window.createOutputChannel('OurDB Viewer');
outputChannel.appendLine('OurDB extension activated');
outputChannel.show(true);
// Register our custom content provider for the ourdb:// scheme
const contentProvider = new OurDBContentProvider();
const contentProviderRegistration = vscode.workspace.registerTextDocumentContentProvider('ourdb', contentProvider);
// Register our custom editor provider
const editorProvider = new OurDBEditorProvider(outputChannel);
const editorRegistration = vscode.window.registerCustomEditorProvider(
'ourdb.viewer',
editorProvider,
{
webviewOptions: { retainContextWhenHidden: true },
supportsMultipleEditorsPerDocument: false
}
);
// Register a command to refresh the view
const refreshCommand = vscode.commands.registerCommand('ourdb.refresh', () => {
contentProvider._onDidChange.fire(vscode.window.activeTextEditor?.document.uri);
});
// Register a command to open .ourdb files
const openOurDBCommand = vscode.commands.registerCommand('ourdb.openFile', (uri) => {
try {
if (!uri) {
outputChannel.appendLine('URI is undefined in openOurDBCommand');
return;
}
outputChannel.appendLine(`Command triggered for: ${uri.fsPath}`);
// Open with the custom editor
vscode.commands.executeCommand('vscode.openWith', uri, 'ourdb.viewer');
outputChannel.appendLine('Opened with custom editor via command');
} catch (error) {
outputChannel.appendLine(`Error in openOurDBCommand: ${error.message}`);
outputChannel.appendLine(error.stack);
}
});
// Register a file open handler for .ourdb files
const fileOpenHandler = vscode.workspace.onDidOpenTextDocument(document => {
try {
// More robust check for document and uri properties
if (!document || !document.uri) {
outputChannel.appendLine('Document or URI is undefined');
return;
}
outputChannel.appendLine(`File opened: ${document.uri.fsPath} (scheme: ${document.uri.scheme})`);
outputChannel.appendLine(`Language ID: ${document.languageId}, Is binary: ${document.isClosed}`);
// Check if uri has necessary properties
if (typeof document.uri.fsPath !== 'string') {
outputChannel.appendLine('File path is not a string');
return;
}
// Check if this is an .ourdb file
if (!document.uri.fsPath.endsWith('.ourdb')) {
outputChannel.appendLine(`Skipping non-ourdb file: ${document.uri.fsPath}`);
return;
}
// Ensure uri has a scheme property before using with()
if (typeof document.uri.scheme !== 'string') {
outputChannel.appendLine('Warning: document.uri.scheme is not defined');
return;
}
// Skip if already using our custom scheme
if (document.uri.scheme === 'ourdb') {
outputChannel.appendLine('Already using ourdb scheme, skipping');
return;
}
outputChannel.appendLine(`Processing .ourdb file: ${document.uri.fsPath}`);
// Open with the custom editor
vscode.commands.executeCommand('vscode.openWith', document.uri, 'ourdb.viewer');
outputChannel.appendLine('Opened with custom editor');
} catch (error) {
outputChannel.appendLine(`Error in fileOpenHandler: ${error.message}`);
outputChannel.appendLine(error.stack);
}
});
// Register a file system watcher for .ourdb files
const watcher = vscode.workspace.createFileSystemWatcher('**/*.ourdb');
watcher.onDidCreate((uri) => {
outputChannel.appendLine(`OurDB file created: ${uri.fsPath}`);
vscode.commands.executeCommand('ourdb.openFile', uri);
});
watcher.onDidChange((uri) => {
outputChannel.appendLine(`OurDB file changed: ${uri.fsPath}`);
if (vscode.window.activeTextEditor &&
vscode.window.activeTextEditor.document.uri.fsPath === uri.fsPath) {
vscode.commands.executeCommand('ourdb.refresh');
}
});
// Add all disposables to subscriptions
context.subscriptions.push(
contentProviderRegistration,
editorRegistration,
refreshCommand,
openOurDBCommand,
fileOpenHandler,
watcher,
outputChannel
);
}
function deactivate() {}
module.exports = {
activate,
deactivate
};

View File

@@ -0,0 +1,68 @@
{
"name": "ourdb-viewer",
"displayName": "OurDB Viewer",
"description": "View OurDB files line by line",
"version": "0.0.1",
"publisher": "local-herolib",
"private": true,
"engines": {
"vscode": "^1.60.0"
},
"categories": [
"Other"
],
"activationEvents": [
"onLanguage:ourdb",
"onFileSystem:ourdb",
"workspaceContains:**/*.ourdb",
"onCommand:ourdb.openFile"
],
"main": "./extension.js",
"contributes": {
"languages": [{
"id": "ourdb",
"extensions": [".ourdb"],
"aliases": ["OurDB"],
"mimetypes": ["application/x-ourdb"]
}],
"commands": [
{
"command": "ourdb.refresh",
"title": "OurDB: Refresh View"
},
{
"command": "ourdb.openFile",
"title": "OurDB: Open File"
}
],
"menus": {
"explorer/context": [
{
"when": "resourceExtname == .ourdb",
"command": "ourdb.openFile",
"group": "navigation"
}
]
},
"customEditors": [
{
"viewType": "ourdb.viewer",
"displayName": "OurDB Viewer",
"selector": [
{ "filenamePattern": "*.ourdb" }
],
"priority": "default"
}
]
},
"scripts": {
"lint": "eslint .",
"pretest": "npm run lint"
},
"devDependencies": {
"@types/vscode": "^1.60.0",
"@types/node": "^14.x.x",
"eslint": "^7.32.0",
"typescript": "^4.4.3"
}
}