diff --git a/package.json b/package.json index f5eb9cd454..933421f5c1 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,11 @@ "vscode-home-assistant.ignoreCertificates": { "type": "boolean", "description": "Enable insecure transport. Check this if you want to connect over an insecure HTTPS transport with a invalid certificate!" + }, + "vscode-home-assistant.searchPath": { + "type": "string", + "description": "The path from which your Home Assistant configuration files will be searched. Empty by default, so all files in your workspace will be evaluated. On large workspaces, setting this specifically to the path where your configuration files are located can improve performance drastically. Path can be absolute or relative to the workspace root.", + "default": "" } } } diff --git a/src/language-service/src/configuration.ts b/src/language-service/src/configuration.ts index b81e74ed27..760926da64 100644 --- a/src/language-service/src/configuration.ts +++ b/src/language-service/src/configuration.ts @@ -3,6 +3,7 @@ import * as vscodeUri from "vscode-uri"; export interface IConfigurationService { isConfigured: boolean; + searchPath?: string; token?: string; url?: string; ignoreCertificates: boolean; @@ -13,11 +14,14 @@ export interface HomeAssistantConfiguration { longLivedAccessToken?: string; hostUrl?: string; ignoreCertificates: boolean; + searchPath?: string; } export class ConfigurationService implements IConfigurationService { public isConfigured = false; + public searchPath?: string; + public token?: string; public url?: string; @@ -41,6 +45,7 @@ export class ConfigurationService implements IConfigurationService { this.url = this.getUri(incoming.hostUrl); } this.ignoreCertificates = !!incoming.ignoreCertificates; + this.searchPath = incoming.searchPath; this.setConfigViaEnvironmentVariables(); @@ -48,6 +53,9 @@ export class ConfigurationService implements IConfigurationService { }; private setConfigViaEnvironmentVariables() { + if (!this.searchPath && process.env.HASS_SEARCH_PATH) { + this.searchPath = this.getUri(process.env.HASS_SEARCH_PATH); + } if (!this.url && process.env.HASS_SERVER) { this.url = this.getUri(process.env.HASS_SERVER); } diff --git a/src/language-service/src/fileAccessor.ts b/src/language-service/src/fileAccessor.ts index badd4504f0..f20e59c26f 100644 --- a/src/language-service/src/fileAccessor.ts +++ b/src/language-service/src/fileAccessor.ts @@ -1,4 +1,5 @@ export interface FileAccessor { + getRoot(): string; getFileContents(fileName: string): Promise; getFilesInFolder(subFolder: string): string[]; getFilesInFolderRelativeFrom( diff --git a/src/language-service/src/haConfig/haConfig.ts b/src/language-service/src/haConfig/haConfig.ts index e7d36d22cc..49899da629 100644 --- a/src/language-service/src/haConfig/haConfig.ts +++ b/src/language-service/src/haConfig/haConfig.ts @@ -99,7 +99,8 @@ export class HomeAssistantConfiguration { }; private getRootFiles = (): string[] => { - const filesInRoot = this.fileAccessor.getFilesInFolder(""); + console.log(`Searching from "${path.join(this.fileAccessor.getRoot(), this.subFolder)}"`); + const filesInRoot = this.fileAccessor.getFilesInFolder(this.subFolder); const ourFiles = [ "configuration.yaml", "ui-lovelace.yaml", diff --git a/src/server/fileAccessor.ts b/src/server/fileAccessor.ts index 9e1d0f58e7..f1a90e8af3 100644 --- a/src/server/fileAccessor.ts +++ b/src/server/fileAccessor.ts @@ -3,22 +3,7 @@ import { TextDocument } from "vscode-languageserver-textdocument"; import * as fs from "fs"; import * as path from "path"; import * as vscodeUri from "vscode-uri"; - -export interface FileAccessor { - getFileContents(fileName: string): Promise; - getFilesInFolder(subFolder: string): string[]; - getFilesInFolderRelativeFrom( - subFolder: string, - relativeFrom: string, - ): string[]; - getFilesInFolderRelativeFromAsFileUri( - subFolder: string, - relativeFrom: string, - ): string[]; - getRelativePath(relativeFrom: string, filename: string): string; - getRelativePathAsFileUri(relativeFrom: string, filename: string): string; - fromUriToLocalPath(uri: string): string; -} +import { FileAccessor } from "../language-service/src/fileAccessor"; export class VsCodeFileAccessor implements FileAccessor { private ourRoot: string; @@ -27,7 +12,11 @@ export class VsCodeFileAccessor implements FileAccessor { private workspaceFolder: string, private documents: TextDocuments, ) { - this.ourRoot = path.resolve(); + this.ourRoot = path.resolve(workspaceFolder); + } + + getRoot(): string { + return this.ourRoot; } public async getFileContents(uri: string): Promise { @@ -58,21 +47,22 @@ export class VsCodeFileAccessor implements FileAccessor { filelist: string[] = [], ): string[] { subFolder = path.normalize(subFolder); + console.log(`fileAccessor.tf:getFilesInFolder:subFolder: ${subFolder}`); try { - fs.readdirSync(subFolder).forEach((file) => { + fs.readdirSync(path.join(this.ourRoot, subFolder)).forEach((file) => { // ignore dot files if (file.charAt(0) === ".") { return; } filelist = - fs.statSync(path.join(subFolder, file)).isDirectory() && + fs.statSync(path.join(this.ourRoot, subFolder, file)).isDirectory() && !file.startsWith(".") ? this.getFilesInFolder(path.join(subFolder, file), filelist) : filelist.concat(path.join(subFolder, file)); }); } catch (err) { - console.log(`Cannot find the files in folder ${subFolder}`); + console.log(`Cannot find the files in folder ${subFolder}: ${err}`); } return filelist; } diff --git a/src/server/server.ts b/src/server/server.ts index 46e7444716..79ac4b27a3 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -32,116 +32,135 @@ connection.onInitialize((params) => { connection.console.log( `[Home Assistant Language Server(${process.pid})] Started and initialize received`, ); - const configurationService = new ConfigurationService(); + const haConnection = new HaConnection(configurationService); - const fileAccessor = new VsCodeFileAccessor(params.rootUri, documents); - const haConfig = new HomeAssistantConfiguration(fileAccessor); - - const definitionProviders = [ - new IncludeDefinitionProvider(fileAccessor), - new ScriptDefinitionProvider(haConfig), - ]; - - const jsonWorkerContributions = [ - new EntityIdCompletionContribution(haConnection), - new ServicesCompletionContribution(haConnection), - ]; - - const schemaServiceForIncludes = new SchemaServiceForIncludes(); - - const yamlLanguageService = getLanguageService( - // eslint-disable-next-line @typescript-eslint/require-await - async () => "", - null, - jsonWorkerContributions, - ); - const sendDiagnostics = (uri: string, diagnostics: Diagnostic[]) => { - connection.sendDiagnostics({ - uri, - diagnostics, - }); - }; + // Wait for configuration to be loaded before initialising the rest + connection.onDidChangeConfiguration(async (config) => { + connection.console.log( + `[Home Assistant Language Server(${process.pid})] didChangeConfiguration received`, + ) + configurationService.updateConfiguration(config); - const discoverFilesAndUpdateSchemas = async () => { - try { - await haConfig.discoverFiles(); - homeAsisstantLanguageService.findAndApplySchemas(); - } catch (e) { - console.error( - `Unexpected error during file discovery / schema configuration: ${e}`, - ); + const haConnection = new HaConnection(configurationService); + console.log(`configurationService.url: ${configurationService.url}`) + console.log(`configurationService.searchPath: ${configurationService.searchPath}`) + console.log(`params.rootUri: ${params.rootUri}`) + let rootUri = params.rootUri; + if (configurationService.searchPath !== undefined) { + rootUri = configurationService.searchPath; } - }; + console.log(`rootUri: ${rootUri}`) + const fileAccessor = new VsCodeFileAccessor(rootUri, documents); + const haConfig = new HomeAssistantConfiguration(fileAccessor); + + const definitionProviders = [ + new IncludeDefinitionProvider(fileAccessor), + new ScriptDefinitionProvider(haConfig), + ]; + + const jsonWorkerContributions = [ + new EntityIdCompletionContribution(haConnection), + new ServicesCompletionContribution(haConnection), + ]; + + const schemaServiceForIncludes = new SchemaServiceForIncludes(); + + const yamlLanguageService = getLanguageService( + // eslint-disable-next-line @typescript-eslint/require-await + async () => "", + null, + jsonWorkerContributions, + ); - const homeAsisstantLanguageService = new HomeAssistantLanguageService( - yamlLanguageService, - haConfig, - haConnection, - definitionProviders, - schemaServiceForIncludes, - sendDiagnostics, - () => { - documents.all().forEach(async (d) => { - const diagnostics = - await homeAsisstantLanguageService.getDiagnostics(d); - sendDiagnostics(d.uri, diagnostics); + const sendDiagnostics = (uri: string, diagnostics: Diagnostic[]) => { + connection.sendDiagnostics({ + uri, + diagnostics, }); - }, - ); + }; + + const discoverFilesAndUpdateSchemas = async () => { + try { + await haConfig.discoverFiles(); + homeAsisstantLanguageService.findAndApplySchemas(); + } catch (e) { + console.error( + `Unexpected error during file discovery / schema configuration: ${e}`, + ); + } + }; + + const homeAsisstantLanguageService = new HomeAssistantLanguageService( + yamlLanguageService, + haConfig, + haConnection, + definitionProviders, + schemaServiceForIncludes, + sendDiagnostics, + () => { + documents.all().forEach(async (d) => { + const diagnostics = await homeAsisstantLanguageService.getDiagnostics( + d, + ); + sendDiagnostics(d.uri, diagnostics); + }); + }, + ); - documents.onDidChangeContent((e) => - homeAsisstantLanguageService.onDocumentChange(e), - ); - documents.onDidOpen((e) => homeAsisstantLanguageService.onDocumentOpen(e)); + documents.onDidChangeContent((e) => + homeAsisstantLanguageService.onDocumentChange(e), + ); + documents.onDidOpen((e) => homeAsisstantLanguageService.onDocumentOpen(e)); - let onDidSaveDebounce: NodeJS.Timer; - documents.onDidSave(() => { - clearTimeout(onDidSaveDebounce); - onDidSaveDebounce = setTimeout(discoverFilesAndUpdateSchemas, 100); - }); + let onDidSaveDebounce: NodeJS.Timer; + documents.onDidSave(() => { + clearTimeout(onDidSaveDebounce); + onDidSaveDebounce = setTimeout(discoverFilesAndUpdateSchemas, 100); + }); - connection.onDocumentSymbol((p) => - homeAsisstantLanguageService.onDocumentSymbol( - documents.get(p.textDocument.uri), - ), - ); - connection.onDocumentFormatting((p) => - homeAsisstantLanguageService.onDocumentFormatting( - documents.get(p.textDocument.uri), - p.options, - ), - ); - connection.onCompletion((p) => - homeAsisstantLanguageService.onCompletion( - documents.get(p.textDocument.uri), - p.position, - ), - ); - connection.onCompletionResolve((p) => - homeAsisstantLanguageService.onCompletionResolve(p), - ); - connection.onHover((p) => - homeAsisstantLanguageService.onHover( - documents.get(p.textDocument.uri), - p.position, - ), - ); - connection.onDefinition((p) => - homeAsisstantLanguageService.onDefinition( - documents.get(p.textDocument.uri), - p.position, - ), - ); + connection.onDocumentSymbol((p) => + homeAsisstantLanguageService.onDocumentSymbol( + documents.get(p.textDocument.uri), + ), + ); + connection.onDocumentFormatting((p) => + homeAsisstantLanguageService.onDocumentFormatting( + documents.get(p.textDocument.uri), + p.options, + ), + ); + connection.onCompletion((p) => + homeAsisstantLanguageService.onCompletion( + documents.get(p.textDocument.uri), + p.position, + ), + ); + connection.onCompletionResolve((p) => + homeAsisstantLanguageService.onCompletionResolve(p), + ); + connection.onHover((p) => + homeAsisstantLanguageService.onHover( + documents.get(p.textDocument.uri), + p.position, + ), + ); + connection.onDefinition((p) => + homeAsisstantLanguageService.onDefinition( + documents.get(p.textDocument.uri), + p.position, + ), + ); - connection.onDidChangeConfiguration(async (config) => { - configurationService.updateConfiguration(config); await haConnection.notifyConfigUpdate(); if (!configurationService.isConfigured) { connection.sendNotification("no-config"); } + + // fire and forget + setTimeout(discoverFilesAndUpdateSchemas, 0); }); connection.onRequest( @@ -179,9 +198,6 @@ connection.onInitialize((params) => { connection.sendNotification("render_template_completed", outputString); }); - // fire and forget - setTimeout(discoverFilesAndUpdateSchemas, 0); - return { capabilities: { textDocumentSync: TextDocumentSyncKind.Full,