Skip to content

Commit

Permalink
Overhaul Config Management (#266)
Browse files Browse the repository at this point in the history
  • Loading branch information
Princesseuh authored Apr 29, 2022
1 parent c4c454d commit 33a2028
Show file tree
Hide file tree
Showing 18 changed files with 496 additions and 339 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"eslint-plugin-prettier": "^3.4.0",
"turbo": "^1.0.0",
"prettier": "^2.2.1",
"typescript": "^4.5.4"
"typescript": "^4.6.0"
},
"engines": {
"node": "^14.16.0 || >=16.0.0"
Expand Down
188 changes: 143 additions & 45 deletions packages/language-server/src/core/config/ConfigManager.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,36 @@
import { get, merge } from 'lodash';
import { VSCodeEmmetConfig } from '@vscode/emmet-helper';
import { LSConfig } from './interfaces';
import { LSConfig, LSCSSConfig, LSHTMLConfig, LSTypescriptConfig } from './interfaces';
import { Connection, DidChangeConfigurationParams } from 'vscode-languageserver';
import { TextDocument } from 'vscode-languageserver-textdocument';
import { FormatCodeSettings, SemicolonPreference, TsConfigSourceFile, UserPreferences } from 'typescript';

const defaultLSConfig: LSConfig = {
astro: {
enabled: true,
diagnostics: { enabled: true },
rename: { enabled: true },
format: { enabled: true },
completions: { enabled: true },
hover: { enabled: true },
codeActions: { enabled: true },
selectionRange: { enabled: true },
},
export const defaultLSConfig: LSConfig = {
typescript: {
enabled: true,
diagnostics: { enabled: true },
hover: { enabled: true },
completions: { enabled: true },
definitions: { enabled: true },
findReferences: { enabled: true },
documentSymbols: { enabled: true },
codeActions: { enabled: true },
rename: { enabled: true },
selectionRange: { enabled: true },
signatureHelp: { enabled: true },
semanticTokens: { enabled: true },
implementation: { enabled: true },
typeDefinition: { enabled: true },
},
css: {
enabled: true,
diagnostics: { enabled: true },
hover: { enabled: true },
completions: { enabled: true, emmet: true },
documentColors: { enabled: true },
colorPresentations: { enabled: true },
documentSymbols: { enabled: true },
selectionRange: { enabled: true },
},
html: {
enabled: true,
hover: { enabled: true },
completions: { enabled: true, emmet: true },
tagComplete: { enabled: true },
documentSymbols: { enabled: true },
renameTags: { enabled: true },
linkedEditing: { enabled: true },
},
};

Expand All @@ -62,46 +46,160 @@ type DeepPartial<T> = T extends Record<string, unknown>
* For more info on this, see the [internal docs](../../../../../docs/internal/language-server/config.md)
*/
export class ConfigManager {
private config: LSConfig = defaultLSConfig;
private emmetConfig: VSCodeEmmetConfig = {};
private globalConfig: Record<string, any> = { astro: defaultLSConfig };
private documentSettings: Record<string, Record<string, Promise<any>>> = {};

private isTrusted = true;

updateConfig(config: DeepPartial<LSConfig>): void {
// Ideally we shouldn't need the merge here because all updates should be valid and complete configs.
// But since those configs come from the client they might be out of synch with the valid config:
// We might at some point in the future forget to synch config settings in all packages after updating the config.
this.config = merge({}, defaultLSConfig, this.config, config);
private connection: Connection | undefined;

constructor(connection?: Connection) {
this.connection = connection;
}

updateEmmetConfig(config: VSCodeEmmetConfig) {
this.emmetConfig = config || {};
updateConfig() {
// Reset all cached document settings
this.documentSettings = {};
}

getEmmetConfig(): VSCodeEmmetConfig {
return this.emmetConfig;
removeDocument(scopeUri: string) {
delete this.documentSettings[scopeUri];
}

/**
* Whether or not specified setting is enabled
* @param key a string which is a path. Example: 'astro.diagnostics.enabled'.
*/
enabled(key: string): boolean {
return !!this.get(key);
async getConfig<T>(section: string, scopeUri: string): Promise<T> {
if (!this.connection) {
return this.globalConfig[section];
}

if (!this.documentSettings[scopeUri]) {
this.documentSettings[scopeUri] = {};
}

if (!this.documentSettings[scopeUri][section]) {
this.documentSettings[scopeUri][section] = await this.connection.workspace.getConfiguration({
scopeUri,
section,
});
}

return this.documentSettings[scopeUri][section];
}

async getEmmetConfig(document: TextDocument): Promise<VSCodeEmmetConfig> {
const emmetConfig = (await this.getConfig<VSCodeEmmetConfig>('emmet', document.uri)) ?? {};

return emmetConfig;
}

async getTSFormatConfig(document: TextDocument): Promise<FormatCodeSettings> {
const formatConfig = (await this.getConfig<FormatCodeSettings>('typescript.format', document.uri)) ?? {};

return {
// We can use \n here since the editor normalizes later on to its line endings.
newLineCharacter: '\n',
insertSpaceAfterCommaDelimiter: formatConfig.insertSpaceAfterCommaDelimiter ?? true,
insertSpaceAfterConstructor: formatConfig.insertSpaceAfterConstructor ?? false,
insertSpaceAfterSemicolonInForStatements: formatConfig.insertSpaceAfterSemicolonInForStatements ?? true,
insertSpaceBeforeAndAfterBinaryOperators: formatConfig.insertSpaceBeforeAndAfterBinaryOperators ?? true,
insertSpaceAfterKeywordsInControlFlowStatements:
formatConfig.insertSpaceAfterKeywordsInControlFlowStatements ?? true,
insertSpaceAfterFunctionKeywordForAnonymousFunctions:
formatConfig.insertSpaceAfterFunctionKeywordForAnonymousFunctions ?? true,
insertSpaceBeforeFunctionParenthesis: formatConfig.insertSpaceBeforeFunctionParenthesis ?? false,
insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis:
formatConfig.insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis ?? false,
insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets:
formatConfig.insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets ?? false,
insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces:
formatConfig.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces ?? true,
insertSpaceAfterOpeningAndBeforeClosingEmptyBraces:
formatConfig.insertSpaceAfterOpeningAndBeforeClosingEmptyBraces ?? true,
insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces:
formatConfig.insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces ?? false,
insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces:
formatConfig.insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces ?? false,
insertSpaceAfterTypeAssertion: formatConfig.insertSpaceAfterTypeAssertion ?? false,
placeOpenBraceOnNewLineForFunctions: formatConfig.placeOpenBraceOnNewLineForFunctions ?? false,
placeOpenBraceOnNewLineForControlBlocks: formatConfig.placeOpenBraceOnNewLineForControlBlocks ?? false,
semicolons: formatConfig.semicolons ?? SemicolonPreference.Ignore,
};
}

async getTSPreferences(document: TextDocument): Promise<UserPreferences> {
const config = (await this.getConfig<any>('typescript', document.uri)) ?? {};
const preferences = (await this.getConfig<any>('typescript.preferences', document.uri)) ?? {};

return {
quotePreference: getQuoteStylePreference(preferences),
importModuleSpecifierPreference: getImportModuleSpecifierPreference(preferences),
importModuleSpecifierEnding: getImportModuleSpecifierEndingPreference(preferences),
allowTextChangesInNewFiles: document.uri.startsWith('file://'),
providePrefixAndSuffixTextForRename:
(preferences.renameShorthandProperties ?? true) === false ? false : preferences.useAliasesForRenames ?? true,
includeAutomaticOptionalChainCompletions: config.suggest?.includeAutomaticOptionalChainCompletions ?? true,
includeCompletionsForImportStatements: config.suggest?.includeCompletionsForImportStatements ?? true,
includeCompletionsWithSnippetText: config.suggest?.includeCompletionsWithSnippetText ?? true,
includeCompletionsForModuleExports: config.suggest?.autoImports ?? true,
allowIncompleteCompletions: true,
includeCompletionsWithInsertText: true,
};
}

/**
* Get a specific setting value
* @param key a string which is a path. Example: 'astro.diagnostics.enable'.
* Return true if a plugin and an optional feature is enabled
*/
get<T>(key: string): T {
return get(this.config, key);
async isEnabled(
document: TextDocument,
plugin: keyof LSConfig,
feature?: keyof LSTypescriptConfig | keyof LSCSSConfig | keyof LSHTMLConfig
): Promise<boolean> {
const config = await this.getConfig<any>('astro', document.uri);

return feature ? config[plugin].enabled && config[plugin][feature].enabled : config[plugin].enabled;
}

/**
* Get the entire user configuration
* Updating the global config should only be done in cases where the client doesn't support `workspace/configuration`
* or inside of tests
*/
getFullConfig(): Readonly<LSConfig> {
return this.config;
updateGlobalConfig(config: DeepPartial<LSConfig>) {
this.globalConfig.astro = merge({}, defaultLSConfig, this.globalConfig.astro, config);
}
}

function getQuoteStylePreference(config: any) {
switch (config.quoteStyle as string) {
case 'single':
return 'single';
case 'double':
return 'double';
default:
return 'auto';
}
}

function getImportModuleSpecifierPreference(config: any) {
switch (config.importModuleSpecifier as string) {
case 'project-relative':
return 'project-relative';
case 'relative':
return 'relative';
case 'non-relative':
return 'non-relative';
default:
return undefined;
}
}

function getImportModuleSpecifierEndingPreference(config: any) {
switch (config.importModuleSpecifierEnding as string) {
case 'minimal':
return 'minimal';
case 'index':
return 'index';
case 'js':
return 'js';
default:
return 'auto';
}
}
53 changes: 0 additions & 53 deletions packages/language-server/src/core/config/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,11 @@
* Make sure that this is kept in sync with the `package.json` of the VS Code extension
*/
export interface LSConfig {
astro: LSAstroConfig;
typescript: LSTypescriptConfig;
html: LSHTMLConfig;
css: LSCSSConfig;
}

export interface LSAstroConfig {
enabled: boolean;
diagnostics: {
enabled: boolean;
};
format: {
enabled: boolean;
};
rename: {
enabled: boolean;
};
completions: {
enabled: boolean;
};
hover: {
enabled: boolean;
};
codeActions: {
enabled: boolean;
};
selectionRange: {
enabled: boolean;
};
}

export interface LSTypescriptConfig {
enabled: boolean;
diagnostics: {
Expand All @@ -48,9 +22,6 @@ export interface LSTypescriptConfig {
completions: {
enabled: boolean;
};
findReferences: {
enabled: boolean;
};
definitions: {
enabled: boolean;
};
Expand All @@ -60,21 +31,12 @@ export interface LSTypescriptConfig {
rename: {
enabled: boolean;
};
selectionRange: {
enabled: boolean;
};
signatureHelp: {
enabled: boolean;
};
semanticTokens: {
enabled: boolean;
};
implementation: {
enabled: boolean;
};
typeDefinition: {
enabled: boolean;
};
}

export interface LSHTMLConfig {
Expand All @@ -92,19 +54,10 @@ export interface LSHTMLConfig {
documentSymbols: {
enabled: boolean;
};
renameTags: {
enabled: boolean;
};
linkedEditing: {
enabled: boolean;
};
}

export interface LSCSSConfig {
enabled: boolean;
diagnostics: {
enabled: boolean;
};
hover: {
enabled: boolean;
};
Expand All @@ -115,13 +68,7 @@ export interface LSCSSConfig {
documentColors: {
enabled: boolean;
};
colorPresentations: {
enabled: boolean;
};
documentSymbols: {
enabled: boolean;
};
selectionRange: {
enabled: boolean;
};
}
Loading

0 comments on commit 33a2028

Please sign in to comment.