Skip to content
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

feat(perf): allow configuring search path #2952

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": ""
}
}
}
Expand Down
8 changes: 8 additions & 0 deletions src/language-service/src/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as vscodeUri from "vscode-uri";

export interface IConfigurationService {
isConfigured: boolean;
searchPath?: string;
token?: string;
url?: string;
ignoreCertificates: boolean;
Expand All @@ -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;
Expand All @@ -41,13 +45,17 @@ export class ConfigurationService implements IConfigurationService {
this.url = this.getUri(incoming.hostUrl);
}
this.ignoreCertificates = !!incoming.ignoreCertificates;
this.searchPath = incoming.searchPath;

this.setConfigViaEnvironmentVariables();

this.isConfigured = `${this.url}` !== "";
};

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);
}
Expand Down
1 change: 1 addition & 0 deletions src/language-service/src/fileAccessor.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export interface FileAccessor {
getRoot(): string;
getFileContents(fileName: string): Promise<string>;
getFilesInFolder(subFolder: string): string[];
getFilesInFolderRelativeFrom(
Expand Down
3 changes: 2 additions & 1 deletion src/language-service/src/haConfig/haConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
30 changes: 10 additions & 20 deletions src/server/fileAccessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>;
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;
Expand All @@ -27,7 +12,11 @@ export class VsCodeFileAccessor implements FileAccessor {
private workspaceFolder: string,
private documents: TextDocuments<TextDocument>,
) {
this.ourRoot = path.resolve();
this.ourRoot = path.resolve(workspaceFolder);
}

getRoot(): string {
return this.ourRoot;
}

public async getFileContents(uri: string): Promise<string> {
Expand Down Expand Up @@ -58,21 +47,22 @@ export class VsCodeFileAccessor implements FileAccessor {
filelist: string[] = [],
): string[] {
subFolder = path.normalize(subFolder);
console.log(`fileAccessor.tf:getFilesInFolder:subFolder: ${subFolder}`);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This helped a lot in understanding what was going on on startup.

TODO: remove this before merge if PR is accepted.


try {
fs.readdirSync(subFolder).forEach((file) => {
fs.readdirSync(path.join(this.ourRoot, subFolder)).forEach((file) => {
pdecat marked this conversation as resolved.
Show resolved Hide resolved
// 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;
}
Expand Down
210 changes: 113 additions & 97 deletions src/server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`)
Comment on lines +47 to +49
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This helped a lot in understanding what was going on on startup.

TODO: remove this before merge if PR is accepted.

let rootUri = params.rootUri;
if (configurationService.searchPath !== undefined) {
rootUri = configurationService.searchPath;
}
};
console.log(`rootUri: ${rootUri}`)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This helped a lot in understanding what was going on on startup.

TODO: remove this before merge if PR is accepted.

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(
Expand Down Expand Up @@ -179,9 +198,6 @@ connection.onInitialize((params) => {
connection.sendNotification("render_template_completed", outputString);
});

// fire and forget
setTimeout(discoverFilesAndUpdateSchemas, 0);

return {
capabilities: <ServerCapabilities>{
textDocumentSync: TextDocumentSyncKind.Full,
Expand Down