-
-
Notifications
You must be signed in to change notification settings - Fork 127
feat: preview zmodel doc #2239
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
feat: preview zmodel doc #2239
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
cf3fc59
feat: preview zmodel doc
jiashengguo 88e4281
update lock file
ymc9 56b7736
fix: resolve comments
jiashengguo 188ff5f
fix: support cursor auth
jiashengguo 8cb98dd
feat: add zenstack logout command
jiashengguo 97fbed1
fix: increase timeout duration for pending authentication to 2 minutes
jiashengguo 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
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}`; | ||
| } | ||
|
|
||
| /** | ||
| * 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
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 |
|---|---|---|
| @@ -1,13 +1,61 @@ | ||
| import { startLanguageServer } from 'langium'; | ||
| import { NodeFileSystem } from 'langium/node'; | ||
| import { createConnection, ProposedFeatures } from 'vscode-languageserver/node'; | ||
| import { URI } from 'vscode-uri'; | ||
| import { createZModelServices } from './zmodel-module'; | ||
| import { eagerLoadAllImports } from '../cli/cli-util'; | ||
|
|
||
| // Create a connection to the client | ||
| const connection = createConnection(ProposedFeatures.all); | ||
|
|
||
| // Inject the shared services and language-specific services | ||
| const { shared } = createZModelServices({ connection, ...NodeFileSystem }); | ||
|
|
||
| // Add custom LSP request handlers | ||
| connection.onRequest('zenstack/getAllImportedZModelURIs', async (params: { textDocument: { uri: string } }) => { | ||
| try { | ||
| const uri = URI.parse(params.textDocument.uri); | ||
| const document = shared.workspace.LangiumDocuments.getOrCreateDocument(uri); | ||
|
|
||
| // Ensure the document is parsed and built | ||
| if (!document.parseResult) { | ||
| await shared.workspace.DocumentBuilder.build([document]); | ||
| } | ||
|
|
||
| // #region merge imported documents | ||
| const langiumDocuments = shared.workspace.LangiumDocuments; | ||
|
|
||
| // load all imports | ||
| const importedURIs = eagerLoadAllImports(document, langiumDocuments); | ||
|
|
||
| const importedDocuments = importedURIs.map((uri) => langiumDocuments.getOrCreateDocument(uri)); | ||
|
|
||
| // build the document together with standard library, plugin modules, and imported documents | ||
| await shared.workspace.DocumentBuilder.build([document, ...importedDocuments], { | ||
| validationChecks: 'all', | ||
| }); | ||
|
|
||
| const hasSyntaxErrors = [uri, ...importedURIs].some((uri) => { | ||
| const doc = langiumDocuments.getOrCreateDocument(uri); | ||
| return ( | ||
| doc.parseResult.lexerErrors.length > 0 || | ||
| doc.parseResult.parserErrors.length > 0 || | ||
| doc.diagnostics?.some((e) => e.severity === 1) | ||
| ); | ||
| }); | ||
|
|
||
| return { | ||
| hasSyntaxErrors, | ||
| importedURIs, | ||
| }; | ||
| } catch (error) { | ||
| console.error('Error getting imported ZModel file:', error); | ||
| return { | ||
| hasSyntaxErrors: true, | ||
| importedURIs: [], | ||
| }; | ||
| } | ||
| }); | ||
|
|
||
| // Start the language server with the shared services | ||
| startLanguageServer(shared); | ||
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.