-
-
Notifications
You must be signed in to change notification settings - Fork 135
feat(vscode): zmodel markdown preview #2506
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
178a08b
feat(vscode): zmodel markdown preview
ymc9 e07b57a
fix: vscode url handling, loading imported models
ymc9 bc64be7
fix: add release notes html
ymc9 6707a71
fix: avoid duplicated doc loading
ymc9 73c6e96
fix: address PR comments about token decoding
ymc9 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
152 changes: 152 additions & 0 deletions
152
packages/ide/vscode/src/extension/documentation-cache.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,152 @@ | ||
| import * as vscode from 'vscode'; | ||
| import { createHash } from 'crypto'; | ||
|
|
||
| // Cache entry interface | ||
| interface CacheEntry { | ||
| data: string; | ||
| timestamp: number; | ||
| extensionVersion: string; | ||
| } | ||
|
|
||
| /** | ||
| * DocumentationCache class handles persistent caching of ZModel documentation | ||
| * using VS Code's globalState for cross-session persistence | ||
| */ | ||
| export class DocumentationCache implements vscode.Disposable { | ||
| private static readonly CACHE_DURATION_MS = 30 * 24 * 60 * 60 * 1000; // 30 days cache duration | ||
| private static readonly CACHE_PREFIX = 'doc-cache.'; | ||
|
|
||
| private extensionContext: vscode.ExtensionContext; | ||
| private extensionVersion: string; | ||
|
|
||
| constructor(context: vscode.ExtensionContext) { | ||
| this.extensionContext = context; | ||
| this.extensionVersion = context.extension.packageJSON.version as string; | ||
| // clear expired cache entries on initialization | ||
| this.clearExpiredCache(); | ||
| } | ||
|
|
||
| /** | ||
| * Dispose of the cache resources (implements vscode.Disposable) | ||
| */ | ||
| dispose(): void {} | ||
|
|
||
| /** | ||
| * Get the cache prefix used for keys | ||
| */ | ||
| getCachePrefix(): string { | ||
| return DocumentationCache.CACHE_PREFIX; | ||
| } | ||
|
|
||
| /** | ||
| * Enable cache synchronization across machines via VS Code Settings Sync | ||
| */ | ||
| private enableCacheSync(): void { | ||
| const cacheKeys = this.extensionContext.globalState | ||
| .keys() | ||
| .filter((key) => key.startsWith(DocumentationCache.CACHE_PREFIX)); | ||
| if (cacheKeys.length > 0) { | ||
| this.extensionContext.globalState.setKeysForSync(cacheKeys); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Generate a cache key from request body with normalized content | ||
| */ | ||
| private generateCacheKey(models: string[]): string { | ||
| // Remove ALL whitespace characters from each model string for cache key generation | ||
| // This ensures identical content with different formatting uses the same cache | ||
| const normalizedModels = models.map((model) => model.replace(/\s/g, '')).sort(); | ||
| const hash = createHash('sha512') | ||
| .update(JSON.stringify({ models: normalizedModels })) | ||
| .digest('hex'); | ||
| return `${DocumentationCache.CACHE_PREFIX}${hash}`; | ||
ymc9 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| /** | ||
| * Check if cache entry is still valid (not expired) | ||
| */ | ||
| private isCacheValid(entry: CacheEntry): boolean { | ||
| return Date.now() - entry.timestamp < DocumentationCache.CACHE_DURATION_MS; | ||
| } | ||
|
|
||
| /** | ||
| * Get cached response if available and valid | ||
| */ | ||
| async getCachedResponse(models: string[]): Promise<string | null> { | ||
| const cacheKey = this.generateCacheKey(models); | ||
| const entry = this.extensionContext.globalState.get<CacheEntry>(cacheKey); | ||
|
|
||
| if (entry && this.isCacheValid(entry)) { | ||
| console.log('Using cached documentation response from persistent storage'); | ||
| return entry.data; | ||
| } | ||
|
|
||
| // Clean up expired entry if it exists | ||
| if (entry) { | ||
| await this.extensionContext.globalState.update(cacheKey, undefined); | ||
| } | ||
|
|
||
| return null; | ||
| } | ||
|
|
||
| /** | ||
| * Cache a response for future use | ||
| */ | ||
| async setCachedResponse(models: string[], data: string): Promise<void> { | ||
| const cacheKey = this.generateCacheKey(models); | ||
| const cacheEntry: CacheEntry = { | ||
| data, | ||
| timestamp: Date.now(), | ||
| extensionVersion: this.extensionVersion, | ||
| }; | ||
|
|
||
| await this.extensionContext.globalState.update(cacheKey, cacheEntry); | ||
|
|
||
| // Update sync keys to include new cache entry | ||
| this.enableCacheSync(); | ||
| } | ||
|
|
||
| /** | ||
| * Clear expired cache entries from persistent storage | ||
| */ | ||
| async clearExpiredCache(): Promise<void> { | ||
| const now = Date.now(); | ||
| let clearedCount = 0; | ||
| const allKeys = this.extensionContext.globalState.keys(); | ||
|
|
||
| for (const key of allKeys) { | ||
| if (key.startsWith(DocumentationCache.CACHE_PREFIX)) { | ||
| const entry = this.extensionContext.globalState.get<CacheEntry>(key); | ||
| if ( | ||
| entry?.extensionVersion !== this.extensionVersion || | ||
| now - entry.timestamp >= DocumentationCache.CACHE_DURATION_MS | ||
| ) { | ||
| await this.extensionContext.globalState.update(key, undefined); | ||
| clearedCount++; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if (clearedCount > 0) { | ||
| console.log(`Cleared ${clearedCount} expired cache entries from persistent storage`); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Clear all cache entries from persistent storage | ||
| */ | ||
| async clearAllCache(): Promise<void> { | ||
| const allKeys = this.extensionContext.globalState.keys(); | ||
| let clearedCount = 0; | ||
|
|
||
| for (const key of allKeys) { | ||
| if (key.startsWith(DocumentationCache.CACHE_PREFIX)) { | ||
| await this.extensionContext.globalState.update(key, undefined); | ||
| clearedCount++; | ||
| } | ||
| } | ||
|
|
||
| console.log(`Cleared all cache entries from persistent storage (${clearedCount} items)`); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
77 changes: 77 additions & 0 deletions
77
packages/ide/vscode/src/extension/release-notes-manager.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| import * as vscode from 'vscode'; | ||
|
|
||
| /** | ||
| * ReleaseNotesManager class handles release notes functionality | ||
| */ | ||
| export class ReleaseNotesManager implements vscode.Disposable { | ||
| private extensionContext: vscode.ExtensionContext; | ||
| private readonly zmodelPreviewReleaseNoteKey = 'zmodel-v3-preview-release-note-shown'; | ||
|
|
||
| constructor(context: vscode.ExtensionContext) { | ||
| this.extensionContext = context; | ||
| this.initialize(); | ||
| } | ||
|
|
||
| /** | ||
| * Initialize and register commands, show release notes if first time | ||
| */ | ||
| initialize(): void { | ||
| this.showReleaseNotesIfFirstTime(); | ||
| } | ||
|
|
||
| /** | ||
| * Show release notes on first activation of this version | ||
| */ | ||
| async showReleaseNotesIfFirstTime(): Promise<void> { | ||
| // Show release notes if this is the first time activating this version | ||
| if (!this.extensionContext.globalState.get(this.zmodelPreviewReleaseNoteKey)) { | ||
| await this.showReleaseNotes(); | ||
| // Update the stored version to prevent showing again | ||
| await this.extensionContext.globalState.update(this.zmodelPreviewReleaseNoteKey, true); | ||
| // Add this key to sync keys for cross-machine synchronization | ||
| this.extensionContext.globalState.setKeysForSync([this.zmodelPreviewReleaseNoteKey]); | ||
ymc9 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Show release notes (can be called manually) | ||
| */ | ||
| async showReleaseNotes(): Promise<void> { | ||
| try { | ||
| // Read the release notes HTML file | ||
| const releaseNotesPath = vscode.Uri.joinPath( | ||
| this.extensionContext.extensionUri, | ||
| 'res/zmodel-v3-preview-release-notes.html', | ||
| ); | ||
|
|
||
| const htmlBytes = await vscode.workspace.fs.readFile(releaseNotesPath); | ||
| const htmlContent = Buffer.from(htmlBytes).toString('utf8'); | ||
| // Create and show the release notes webview | ||
| const panel = vscode.window.createWebviewPanel( | ||
| 'ZenstackReleaseNotes', | ||
| 'ZenStack - New Feature Announcement!', | ||
| vscode.ViewColumn.One, | ||
| { | ||
| enableScripts: true, | ||
| retainContextWhenHidden: true, | ||
| }, | ||
| ); | ||
|
|
||
| panel.webview.html = htmlContent; | ||
|
|
||
| // Optional: Close the panel when user clicks outside or after some time | ||
| panel.onDidDispose(() => { | ||
| // Panel disposed | ||
| }); | ||
| } catch (error) { | ||
| console.error('Error showing release notes:', error); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Dispose of resources | ||
| */ | ||
| dispose(): void { | ||
| // Any cleanup if needed | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.