Highly ambitious ATProtocol AppView service and sdks

Improve intellisense language server #6

This change: - Improves finding & filtering of lexicon files - Adds a logger for better debugging - Unsubscribes from the workspace events when enableValidation is false - Removes duplication between extension.ts and language-server.ts

I'd noticed an issue where if the workspace root was containing the lexicon files, then it was validating the .vscode configuration directory, and also files that were absolutely not lexicon files.

A future improvement may be to add configurable filtering of known "non-lexicon" files, like package.json, tsconfig.json, etc.

Labels

None yet.

assignee

None yet.

Participants 1
AT URI
at://did:plc:5w4eqcxzw5jv5qfnmzxcakfy/sh.tangled.repo.pull/3md22rv3upo22
+369 -156
Diff #0
+55 -67
packages/lexicon-intellisense/src/extension.ts
··· 1 - import * as vscode from 'vscode'; 2 - import * as path from 'path'; 3 - import { LexiconLanguageServer } from './language-server'; 4 5 let languageServer: LexiconLanguageServer | undefined; 6 ··· 12 languageServer.start(); 13 14 // Register commands 15 - const validateCommand = vscode.commands.registerCommand('lexiconIntelliSense.validateFile', async () => { 16 - const activeEditor = vscode.window.activeTextEditor; 17 - if (!activeEditor) { 18 - vscode.window.showErrorMessage('No active editor found'); 19 - return; 20 - } 21 - 22 - const document = activeEditor.document; 23 - if (!isLexiconFile(document)) { 24 - vscode.window.showErrorMessage('Current file is not a lexicon JSON file'); 25 - return; 26 - } 27 - 28 - try { 29 - await languageServer?.validateDocument(document); 30 - vscode.window.showInformationMessage('Lexicon validation completed'); 31 - } catch (error) { 32 - vscode.window.showErrorMessage(`Validation failed: ${error instanceof Error ? error.message : String(error)}`); 33 - } 34 - }); 35 36 - const validateWorkspaceCommand = vscode.commands.registerCommand('lexiconIntelliSense.validateWorkspace', async () => { 37 - if (!languageServer) { 38 - vscode.window.showErrorMessage('Language server not initialized'); 39 - return; 40 - } 41 42 - try { 43 - const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; 44 - if (!workspaceFolder) { 45 - vscode.window.showErrorMessage('No workspace folder found'); 46 return; 47 } 48 49 - await languageServer.validateWorkspace(workspaceFolder.uri); 50 - vscode.window.showInformationMessage('Workspace lexicon validation completed'); 51 - } catch (error) { 52 - vscode.window.showErrorMessage(`Workspace validation failed: ${error instanceof Error ? error.message : String(error)}`); 53 - } 54 - }); 55 56 context.subscriptions.push(validateCommand, validateWorkspaceCommand); 57 - 58 - // Watch for configuration changes 59 - const configWatcher = vscode.workspace.onDidChangeConfiguration((event) => { 60 - if (event.affectsConfiguration('lexiconIntelliSense')) { 61 - languageServer?.updateConfiguration(); 62 - } 63 - }); 64 - 65 - context.subscriptions.push(configWatcher); 66 } 67 68 export function deactivate() { ··· 71 languageServer = undefined; 72 } 73 } 74 - 75 - function isLexiconFile(document: vscode.TextDocument): boolean { 76 - // Check if file is in lexicons directory or has lexicon-like content 77 - const filePath = document.uri.fsPath; 78 - const isInLexiconsDir = filePath.includes('/lexicons/') || filePath.includes('\\lexicons\\'); 79 - 80 - if (isInLexiconsDir && document.languageId === 'json') { 81 - return true; 82 - } 83 - 84 - // Check if JSON content looks like a lexicon 85 - try { 86 - const content = JSON.parse(document.getText()); 87 - return content && typeof content === 'object' && 88 - typeof content.id === 'string' && 89 - content.defs; 90 - } catch { 91 - return false; 92 - } 93 - }
··· 1 + import * as vscode from "vscode"; 2 + import * as path from "path"; 3 + import { LexiconLanguageServer } from "./language-server"; 4 5 let languageServer: LexiconLanguageServer | undefined; 6 ··· 12 languageServer.start(); 13 14 // Register commands 15 + const validateCommand = vscode.commands.registerCommand( 16 + "lexiconIntelliSense.validateFile", 17 + async () => { 18 + const activeEditor = vscode.window.activeTextEditor; 19 + if (!activeEditor) { 20 + vscode.window.showErrorMessage("No active editor found"); 21 + return; 22 + } 23 24 + const document = activeEditor.document; 25 + if (!languageServer?.isLexiconDocument(document)) { 26 + vscode.window.showErrorMessage( 27 + "Current file is not a lexicon JSON file", 28 + ); 29 + return; 30 + } 31 32 + try { 33 + await languageServer?.validateDocument(document); 34 + vscode.window.showInformationMessage("Lexicon validation completed"); 35 + } catch (error) { 36 + vscode.window.showErrorMessage( 37 + `Validation failed: ${error instanceof Error ? error.message : String(error)}`, 38 + ); 39 + } 40 + }, 41 + ); 42 + 43 + const validateWorkspaceCommand = vscode.commands.registerCommand( 44 + "lexiconIntelliSense.validateWorkspace", 45 + async () => { 46 + if (!languageServer) { 47 + vscode.window.showErrorMessage("Language server not initialized"); 48 return; 49 } 50 51 + try { 52 + if ( 53 + !vscode.workspace.workspaceFolders || 54 + vscode.workspace.workspaceFolders.length <= 0 55 + ) { 56 + vscode.window.showErrorMessage("No workspace folders found"); 57 + return; 58 + } 59 + 60 + await languageServer.validateWorkspaceFolders(); 61 + 62 + vscode.window.showInformationMessage( 63 + "Workspace lexicon validation completed", 64 + ); 65 + } catch (error) { 66 + vscode.window.showErrorMessage( 67 + `Workspace validation failed: ${error instanceof Error ? error.message : String(error)}`, 68 + ); 69 + } 70 + }, 71 + ); 72 73 context.subscriptions.push(validateCommand, validateWorkspaceCommand); 74 } 75 76 export function deactivate() { ··· 79 languageServer = undefined; 80 } 81 }
+234 -89
packages/lexicon-intellisense/src/language-server.ts
··· 1 import * as vscode from "vscode"; 2 import * as path from "path"; 3 - import * as fs from "fs"; 4 - import { 5 - LanguageClient, 6 - LanguageClientOptions, 7 - ServerOptions, 8 - TransportKind, 9 - } from "vscode-languageclient/node"; 10 - import { 11 - LexiconValidator, 12 - ValidationError, 13 - validate, 14 - validateWithDetails, 15 - type LexiconDoc, 16 - } from "./lexicon"; 17 18 export class LexiconLanguageServer { 19 private client: LanguageClient | undefined; 20 private context: vscode.ExtensionContext; 21 private diagnosticCollection: vscode.DiagnosticCollection; 22 23 constructor(context: vscode.ExtensionContext) { 24 this.context = context; 25 this.diagnosticCollection = 26 vscode.languages.createDiagnosticCollection("lexicon"); 27 context.subscriptions.push(this.diagnosticCollection); 28 } 29 30 async start() { 31 - // Set up document change listeners for real-time validation 32 - const documentSelector = [ 33 - { scheme: "file", language: "json", pattern: "**/lexicons/**/*.json" }, 34 - ]; 35 36 // Listen for document changes 37 const changeListener = vscode.workspace.onDidChangeTextDocument( 38 async (event) => { 39 - if (this.isLexiconDocument(event.document)) { 40 await this.validateDocument(event.document); 41 } 42 - } 43 ); 44 45 // Listen for document saves 46 const saveListener = vscode.workspace.onDidSaveTextDocument( 47 async (document) => { 48 - if (this.isLexiconDocument(document)) { 49 await this.validateDocument(document); 50 // Re-run workspace validation to check cross-references 51 - if (vscode.workspace.workspaceFolders) { 52 - for (const workspaceFolder of vscode.workspace.workspaceFolders) { 53 - await this.validateWorkspace(workspaceFolder.uri); 54 - } 55 - } 56 } 57 - } 58 ); 59 60 - this.context.subscriptions.push(changeListener, saveListener); 61 - 62 - // Validate all lexicon documents in the workspace 63 - if (vscode.workspace.workspaceFolders) { 64 - for (const workspaceFolder of vscode.workspace.workspaceFolders) { 65 - await this.validateWorkspace(workspaceFolder.uri); 66 - } 67 - } 68 } 69 70 - stop() { 71 - if (this.client) { 72 - this.client.stop(); 73 - this.client = undefined; 74 } 75 } 76 77 - updateConfiguration() { 78 - // Revalidate all open documents when configuration changes 79 for (const document of vscode.workspace.textDocuments) { 80 - if (this.isLexiconDocument(document)) { 81 this.validateDocument(document); 82 } 83 } 84 } 85 86 - async validateDocument(document: vscode.TextDocument) { 87 - const config = vscode.workspace.getConfiguration("lexiconIntelliSense"); 88 - if (!config.get<boolean>("enableValidation", true)) { 89 - console.log("Validation disabled in config"); 90 - return; 91 } 92 93 const diagnostics: vscode.Diagnostic[] = []; 94 95 try { ··· 102 this.createDiagnostic( 103 new vscode.Range(0, 0, 0, 0), 104 "Lexicon must be an object", 105 - vscode.DiagnosticSeverity.Error 106 - ) 107 ); 108 } else { 109 // Validate required fields ··· 112 this.createDiagnostic( 113 new vscode.Range(0, 0, 0, 0), 114 'Lexicon must have a valid "id" field', 115 - vscode.DiagnosticSeverity.Error 116 - ) 117 ); 118 } 119 ··· 122 this.createDiagnostic( 123 new vscode.Range(0, 0, 0, 0), 124 'Lexicon must have a "defs" object', 125 - vscode.DiagnosticSeverity.Error 126 - ) 127 ); 128 } 129 } ··· 136 ? parseError.message 137 : String(parseError) 138 }`, 139 - vscode.DiagnosticSeverity.Error 140 - ) 141 ); 142 } 143 144 - console.log("Setting diagnostics:", diagnostics.length, "errors found"); 145 this.diagnosticCollection.set(document.uri, diagnostics); 146 } 147 148 async validateWorkspace(workspaceUri: vscode.Uri) { 149 - const config = vscode.workspace.getConfiguration("lexiconIntelliSense"); 150 - const lexiconDir = config.get<string>("lexiconDirectory", "lexicons"); 151 152 - const lexiconPath = path.join(workspaceUri.fsPath, lexiconDir); 153 - if (!fs.existsSync(lexiconPath)) { 154 // Silently return if lexicons directory doesn't exist - not all projects use lexicons 155 return; 156 } ··· 164 const document = await vscode.workspace.openTextDocument(uri); 165 await this.validateDocument(document); 166 } catch (error) { 167 - console.warn(`Failed to validate lexicon ${filePath}:`, error); 168 } 169 } 170 ··· 174 175 for (const filePath of lexiconFiles) { 176 try { 177 - const content = fs.readFileSync(filePath, "utf8"); 178 const lexicon = JSON.parse(content); 179 - console.log(lexicon.id, "loaded from", filePath); 180 if (lexicon.id && lexicon.defs) { 181 lexicons.push(lexicon); 182 idToFileMap.set(lexicon.id, filePath); 183 } 184 } catch (error) { 185 - debugger; 186 - console.warn(`Failed to load lexicon ${filePath}:`, error); 187 } 188 } 189 ··· 209 this.createDiagnostic( 210 new vscode.Range(0, 0, 0, 0), 211 error, 212 - vscode.DiagnosticSeverity.Error 213 - ) 214 ); 215 } 216 ··· 222 const totalErrors = errorsByLexiconId 223 ? Object.values(errorsByLexiconId).reduce( 224 (sum: number, errors) => sum + (errors as string[]).length, 225 - 0 226 ) 227 : 0; 228 if (totalErrors === 0) { 229 - console.log( 230 - `Successfully validated ${lexicons.length} lexicon files` 231 ); 232 } else { 233 - const errorCount = errorsByLexiconId ? Object.keys(errorsByLexiconId).length : 0; 234 - console.log( 235 - `Validation found ${totalErrors} errors across ${errorCount} lexicons` 236 ); 237 } 238 } catch (error) { 239 - console.warn( 240 - `WASM validation failed: ${ 241 - error instanceof Error ? error.message : String(error) 242 - }` 243 - ); 244 } 245 } 246 } ··· 248 private async findLexiconFiles(directory: string): Promise<string[]> { 249 const files: string[] = []; 250 251 - const readDir = async (dir: string) => { 252 - const entries = fs.readdirSync(dir, { withFileTypes: true }); 253 for (const entry of entries) { 254 const fullPath = path.join(dir, entry.name); 255 if (entry.isDirectory()) { 256 - await readDir(fullPath); 257 - } else if (entry.name.endsWith(".json")) { 258 - files.push(fullPath); 259 } 260 } 261 }; 262 263 - await readDir(directory); 264 return files; 265 } 266 267 - private isLexiconDocument(document: vscode.TextDocument): boolean { 268 const filePath = document.uri.fsPath; 269 - const isInLexiconsDir = 270 - filePath.includes("/lexicons/") || filePath.includes("\\lexicons\\"); 271 272 - if (isInLexiconsDir && document.languageId === "json") { 273 return true; 274 } 275 ··· 279 return ( 280 content && 281 typeof content === "object" && 282 typeof content.id === "string" && 283 content.defs 284 ); ··· 290 private createDiagnostic( 291 range: vscode.Range, 292 message: string, 293 - severity: vscode.DiagnosticSeverity 294 ): vscode.Diagnostic { 295 const diagnostic = new vscode.Diagnostic(range, message, severity); 296 diagnostic.source = "lexicon-intellisense";
··· 1 import * as vscode from "vscode"; 2 import * as path from "path"; 3 + import * as fs from "fs/promises"; 4 + import { LanguageClient } from "vscode-languageclient/node"; 5 + import { validate, type LexiconDoc } from "./lexicon"; 6 + import Logger from "./logger"; 7 + 8 + type LexiconLanguageServerConfiguration = { 9 + enableValidation: boolean; 10 + lexiconDirectory: string; 11 + }; 12 + 13 + async function pathExists(path: string): Promise<boolean> { 14 + return await fs 15 + .access(path, fs.constants.F_OK) 16 + .then(() => true) 17 + .catch(() => false); 18 + } 19 + 20 + function normalizePath(inputPath: string) { 21 + return inputPath.replace("\\", path.sep).replace("/", path.sep); 22 + } 23 24 export class LexiconLanguageServer { 25 private client: LanguageClient | undefined; 26 private context: vscode.ExtensionContext; 27 private diagnosticCollection: vscode.DiagnosticCollection; 28 + private configuration: LexiconLanguageServerConfiguration; 29 + private logger: Logger; 30 + private workspaceSubscription?: vscode.Disposable; 31 32 constructor(context: vscode.ExtensionContext) { 33 this.context = context; 34 + this.logger = new Logger("lexicon"); 35 this.diagnosticCollection = 36 vscode.languages.createDiagnosticCollection("lexicon"); 37 context.subscriptions.push(this.diagnosticCollection); 38 + 39 + // Special disposable for disposing of workspace subscriptions if they exist: 40 + context.subscriptions.push({ 41 + dispose: () => { 42 + if (this.workspaceSubscription) { 43 + this.workspaceSubscription.dispose(); 44 + this.workspaceSubscription = undefined; 45 + } 46 + }, 47 + }); 48 + 49 + this.configuration = this.loadConfiguration(); 50 } 51 52 async start() { 53 + // Setup watcher for the configuration changing: 54 + const configWatcher = vscode.workspace.onDidChangeConfiguration( 55 + async (event) => { 56 + if (event.affectsConfiguration("lexiconIntelliSense")) { 57 + this.logger.debug("Reloading configuration"); 58 + this.loadConfiguration(); 59 + 60 + if (!this.configuration.enableValidation) { 61 + this.logger.info( 62 + "Lexicon validation disabled in config, set lexiconIntelliSense.enableValidation to true to enable", 63 + ); 64 + 65 + if (this.workspaceSubscription) { 66 + this.workspaceSubscription.dispose(); 67 + this.workspaceSubscription = undefined; 68 + } 69 + 70 + return; 71 + } else if (!this.workspaceSubscription) { 72 + this.workspaceSubscription = this.subscribeToWorkspaceEvents(); 73 + } 74 + 75 + // Revalidate all documents based on new configuration: 76 + await this.validateAllOpenDocuments(); 77 + 78 + // Revalidate the entire workspace: 79 + await this.validateWorkspaceFolders(); 80 + } 81 + }, 82 + ); 83 + 84 + this.context.subscriptions.push(configWatcher); 85 + 86 + if (!this.configuration.enableValidation) { 87 + this.logger.info( 88 + "Lexicon validation disabled in config, set lexiconIntelliSense.enableValidation to true to enable", 89 + ); 90 + 91 + return; 92 + } 93 + 94 + this.workspaceSubscription = this.subscribeToWorkspaceEvents(); 95 96 + // Validate all open documents: 97 + await this.validateAllOpenDocuments(); 98 + 99 + // Validate all lexicon documents in the workspace 100 + await this.validateWorkspaceFolders(); 101 + } 102 + 103 + stop() { 104 + if (this.client) { 105 + this.client.stop(); 106 + this.client = undefined; 107 + } 108 + } 109 + 110 + subscribeToWorkspaceEvents(): vscode.Disposable { 111 + this.logger.debug("Subscribing to workspace events"); 112 // Listen for document changes 113 const changeListener = vscode.workspace.onDidChangeTextDocument( 114 async (event) => { 115 + if (this.canValidateDocument(event.document)) { 116 await this.validateDocument(event.document); 117 } 118 + }, 119 ); 120 121 // Listen for document saves 122 const saveListener = vscode.workspace.onDidSaveTextDocument( 123 async (document) => { 124 + if (this.canValidateDocument(document)) { 125 await this.validateDocument(document); 126 + 127 // Re-run workspace validation to check cross-references 128 + await this.validateWorkspaceFolders(); 129 } 130 + }, 131 ); 132 133 + return vscode.Disposable.from(saveListener, changeListener, { 134 + dispose: () => { 135 + this.logger.debug("Unsubscribing from workspace events"); 136 + }, 137 + }); 138 } 139 140 + loadConfiguration() { 141 + const config = vscode.workspace.getConfiguration("lexiconIntelliSense"); 142 + const enableValidation = !!config.get<boolean>("enableValidation", true); 143 + let lexiconDirectory = config.get<string>("lexiconDirectory", "/lexicons"); 144 + 145 + if (typeof lexiconDirectory !== "string" || lexiconDirectory.length === 0) { 146 + this.logger.warn( 147 + "Configuration value lexiconDirectory was not a string or was empty, using default: lexicons", 148 + ); 149 + lexiconDirectory = "/lexicons"; 150 } 151 + 152 + // Normalise to path configuration to match system path separator: 153 + lexiconDirectory = normalizePath(lexiconDirectory); 154 + 155 + this.configuration = { 156 + enableValidation, 157 + lexiconDirectory, 158 + }; 159 + 160 + return this.configuration; 161 } 162 163 + async validateAllOpenDocuments() { 164 + this.logger.debug("Validating all open documents"); 165 + // Revalidate all open documents: 166 for (const document of vscode.workspace.textDocuments) { 167 + if (this.canValidateDocument(document)) { 168 this.validateDocument(document); 169 } 170 } 171 } 172 173 + private canValidateDocument(document: vscode.TextDocument) { 174 + // Allow validation of new files that are JSON: 175 + if (document.isUntitled) { 176 + return document.isDirty && document.languageId === "json"; 177 + } else { 178 + // Allow validation of saved files: 179 + return this.isLexiconDocument(document); 180 } 181 + } 182 183 + async validateDocument(document: vscode.TextDocument) { 184 const diagnostics: vscode.Diagnostic[] = []; 185 186 try { ··· 193 this.createDiagnostic( 194 new vscode.Range(0, 0, 0, 0), 195 "Lexicon must be an object", 196 + vscode.DiagnosticSeverity.Error, 197 + ), 198 ); 199 } else { 200 // Validate required fields ··· 203 this.createDiagnostic( 204 new vscode.Range(0, 0, 0, 0), 205 'Lexicon must have a valid "id" field', 206 + vscode.DiagnosticSeverity.Error, 207 + ), 208 ); 209 } 210 ··· 213 this.createDiagnostic( 214 new vscode.Range(0, 0, 0, 0), 215 'Lexicon must have a "defs" object', 216 + vscode.DiagnosticSeverity.Error, 217 + ), 218 ); 219 } 220 } ··· 227 ? parseError.message 228 : String(parseError) 229 }`, 230 + vscode.DiagnosticSeverity.Error, 231 + ), 232 ); 233 } 234 235 + this.logger.debug( 236 + `Setting diagnostics: ${diagnostics.length} errors found`, 237 + ); 238 this.diagnosticCollection.set(document.uri, diagnostics); 239 } 240 241 + async validateWorkspaceFolders() { 242 + if (vscode.workspace.workspaceFolders) { 243 + for (const workspaceFolder of vscode.workspace.workspaceFolders) { 244 + await this.validateWorkspace(workspaceFolder.uri); 245 + } 246 + } 247 + } 248 + 249 async validateWorkspace(workspaceUri: vscode.Uri) { 250 + const lexiconPath = path.resolve( 251 + workspaceUri.fsPath, 252 + this.configuration.lexiconDirectory, 253 + ); 254 255 + if (!(await pathExists(lexiconPath))) { 256 // Silently return if lexicons directory doesn't exist - not all projects use lexicons 257 return; 258 } ··· 266 const document = await vscode.workspace.openTextDocument(uri); 267 await this.validateDocument(document); 268 } catch (error) { 269 + this.logger.error(`Failed to validate lexicon ${filePath}:`, error); 270 } 271 } 272 ··· 276 277 for (const filePath of lexiconFiles) { 278 try { 279 + const content = await fs.readFile(filePath, "utf8"); 280 const lexicon = JSON.parse(content); 281 + this.logger.debug(`${lexicon.id} loaded from ${filePath}`); 282 if (lexicon.id && lexicon.defs) { 283 lexicons.push(lexicon); 284 idToFileMap.set(lexicon.id, filePath); 285 } 286 } catch (error) { 287 + this.logger.error(`Failed to load lexicon ${filePath}`, error); 288 } 289 } 290 ··· 310 this.createDiagnostic( 311 new vscode.Range(0, 0, 0, 0), 312 error, 313 + vscode.DiagnosticSeverity.Error, 314 + ), 315 ); 316 } 317 ··· 323 const totalErrors = errorsByLexiconId 324 ? Object.values(errorsByLexiconId).reduce( 325 (sum: number, errors) => sum + (errors as string[]).length, 326 + 0, 327 ) 328 : 0; 329 + 330 if (totalErrors === 0) { 331 + this.logger.info( 332 + `Successfully validated ${lexicons.length} lexicon files`, 333 ); 334 } else { 335 + const errorCount = errorsByLexiconId 336 + ? Object.keys(errorsByLexiconId).length 337 + : 0; 338 + 339 + this.logger.info( 340 + `Validation found ${totalErrors} errors across ${errorCount} lexicons`, 341 ); 342 } 343 } catch (error) { 344 + this.logger.error(`WASM validation failed`, error); 345 } 346 } 347 } ··· 349 private async findLexiconFiles(directory: string): Promise<string[]> { 350 const files: string[] = []; 351 352 + this.logger.debug("finding lexicon files: " + directory); 353 + 354 + const readDir = async (dir: string, isRoot: boolean) => { 355 + const entries = await fs.readdir(dir, { withFileTypes: true }); 356 for (const entry of entries) { 357 const fullPath = path.join(dir, entry.name); 358 + 359 + // If we can't read the path, skip it: 360 + const isReadable = await pathExists(fullPath); 361 + if (!isReadable) { 362 + continue; 363 + } 364 + 365 if (entry.isDirectory()) { 366 + // ignore .vscode directory in the root, these are almost certainly 367 + // configuration files: 368 + if (entry.name === ".vscode" && isRoot) { 369 + continue; 370 + } 371 + 372 + // Don't recurse into the node_modules directory: 373 + if (entry.name === "node_modules") { 374 + continue; 375 + } 376 + 377 + await readDir(fullPath, false); 378 + } else if (entry.isFile()) { 379 + if (entry.name.endsWith(".json")) { 380 + files.push(fullPath); 381 + } 382 } 383 } 384 }; 385 386 + await readDir(directory, true); 387 + 388 return files; 389 } 390 391 + isLexiconFilePath(filePath: string): boolean { 392 + const normalized = normalizePath(filePath); 393 + if (!filePath.endsWith(".json")) { 394 + return false; 395 + } 396 + 397 + if ( 398 + this.configuration.lexiconDirectory !== "./" && 399 + this.configuration.lexiconDirectory !== "/" 400 + ) { 401 + // normalized is an absolute file path, so we're doing string inclusion, 402 + // rather than startsWith: 403 + return normalized.includes(this.configuration.lexiconDirectory); 404 + } 405 + 406 + return true; 407 + } 408 + 409 + isLexiconDocument(document: vscode.TextDocument): boolean { 410 const filePath = document.uri.fsPath; 411 + if (document.languageId !== "json") { 412 + return false; 413 + } 414 415 + if (this.isLexiconFilePath(filePath)) { 416 return true; 417 } 418 ··· 422 return ( 423 content && 424 typeof content === "object" && 425 + typeof content.lexicon === "number" && 426 + content.lexicon === 1 && 427 typeof content.id === "string" && 428 content.defs 429 ); ··· 435 private createDiagnostic( 436 range: vscode.Range, 437 message: string, 438 + severity: vscode.DiagnosticSeverity, 439 ): vscode.Diagnostic { 440 const diagnostic = new vscode.Diagnostic(range, message, severity); 441 diagnostic.source = "lexicon-intellisense";
+80
packages/lexicon-intellisense/src/logger.ts
···
··· 1 + // Based on MIT/Apache licensed file from: 2 + // https://github.com/biomejs/biome-vscode/blob/main/src/logger.ts 3 + import { type LogOutputChannel, window } from "vscode"; 4 + 5 + export default class Logger { 6 + /** 7 + * The output channel for logging messages. 8 + */ 9 + private outputChannel: LogOutputChannel; 10 + 11 + /** 12 + * Creates a new logger for the given Biome instance 13 + */ 14 + constructor(private readonly name: string) { 15 + this.outputChannel = window.createOutputChannel(name, { 16 + log: true, 17 + }); 18 + } 19 + 20 + public show(preserveFocus: boolean = false): void { 21 + this.outputChannel.show(preserveFocus); 22 + } 23 + 24 + /** 25 + * Logs a message to the output channel. 26 + * 27 + * @param message The message to log. 28 + */ 29 + public info(message: string): void { 30 + this.outputChannel?.info(` ${message}`); 31 + } 32 + 33 + /** 34 + * Logs an error message to the output channel. 35 + * 36 + * @param message The error message to log. 37 + */ 38 + public error(message?: string, error?: unknown): void { 39 + if (error !== undefined) { 40 + const errorMessage = 41 + error instanceof Error ? error.message : String(error); 42 + 43 + if (errorMessage) { 44 + this.outputChannel.error(` ${message ?? ""}:\n${errorMessage}`); 45 + return; 46 + } 47 + } 48 + 49 + if (message) { 50 + this.outputChannel.error(` ${message}`); 51 + } 52 + } 53 + 54 + /** 55 + * Logs a warning message to the output channel. 56 + * 57 + * @param message The warning message to log. 58 + */ 59 + public warn(message?: string): void { 60 + this.outputChannel.warn(message ?? ""); 61 + } 62 + 63 + /** 64 + * Logs a debug message to the output channel. 65 + * 66 + * @param message The debug message to log. 67 + */ 68 + public debug(message?: string): void { 69 + this.outputChannel.debug(message ?? ""); 70 + } 71 + 72 + /** 73 + * Logs a verbose message to the output channel. 74 + * 75 + * @param message The verbose message to log. 76 + */ 77 + public trace(message?: string): void { 78 + this.outputChannel.trace(message ?? ""); 79 + } 80 + }

History

1 round 0 comments
sign up or login to add to the discussion
1 commit
expand
lexicon-itellisense: Improve language server
no conflicts, ready to merge
expand 0 comments