Skip to content

Commit

Permalink
fix(mapping): complete mappings refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
Chris authored and Chris committed May 27, 2020
1 parent aa0d6c0 commit 12f5488
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 170 deletions.
159 changes: 20 additions & 139 deletions src/configuration/configuration.service.ts
Original file line number Diff line number Diff line change
@@ -1,155 +1,36 @@
import * as vscode from "vscode";
import * as JSON5 from "json5";
import { readFileSync } from "fs";
import { Config, Mapping } from "./configuration.interface";
import { getWorkfolderTsConfigConfiguration } from "./tsconfig.service";
import { parseMappings, replaceWorkspaceRoot } from "./mapping.service";

let cachedConfiguration = new Map<string, Config>();
export async function getConfiguration(
resource: vscode.Uri
): Promise<Readonly<Config>> {
const workspaceFolder = vscode.workspace.getWorkspaceFolder(resource);

export async function subscribeToConfigurationService(): Promise<
vscode.Disposable[]
> {
await preFetchConfiguration();
const getConfig = (key: string) =>
vscode.workspace.getConfiguration(key, resource);

const disposables: vscode.Disposable[] = [];
for (const workfolder of vscode.workspace.workspaceFolders || []) {
const pattern = new vscode.RelativePattern(workfolder, "[tj]sconfig.json");
const fileWatcher = vscode.workspace.createFileSystemWatcher(pattern);
fileWatcher.onDidChange(preFetchConfiguration);
disposables.push(fileWatcher);
}

const configurationWatcher = vscode.workspace.onDidChangeConfiguration(
preFetchConfiguration
);
disposables.push(configurationWatcher);

return disposables;
}

export function getConfiguration(): Readonly<Config> {
const currentFile = vscode.window.activeTextEditor?.document.uri;

if (!currentFile) {
return getWorkspaceConfiguration();
}

const workSpaceKey = vscode.workspace.getWorkspaceFolder(currentFile)?.name;

if (!workSpaceKey) {
return getWorkspaceConfiguration();
}

return cachedConfiguration.get(workSpaceKey) || getWorkspaceConfiguration();
}

async function preFetchConfiguration() {
for (const workfolder of vscode.workspace.workspaceFolders || []) {
const configuration = await getConfigurationForWorkfolder(workfolder);
cachedConfiguration.set(workfolder.name, configuration);
}
}

async function getConfigurationForWorkfolder(
workfolder: vscode.WorkspaceFolder
): Promise<Config> {
const workspaceConfiguration = getWorkspaceConfiguration();
const workfolderTSConfig = await getWorkfolderTsConfigConfiguration(
workfolder
);
return {
...workspaceConfiguration,
...workfolderTSConfig
};
}

async function getWorkfolderTsConfigConfiguration(
workfolder: vscode.WorkspaceFolder
): Promise<Partial<Config>> {
const include = new vscode.RelativePattern(workfolder, "[tj]sconfig.json");
const exclude = new vscode.RelativePattern(workfolder, "**/node_modules/**");

const files = await vscode.workspace.findFiles(include, exclude);

const mappingsFromWorkspaceConfig = files.reduce(
(mappings: Mapping[], file) => {
try {
const parsedFile = JSON5.parse(readFileSync(file.fsPath).toString());
const newMappings = createMappingsFromWorkspaceConfig(parsedFile);
return [...mappings, ...newMappings];
} catch (error) {
return mappings;
}
},
[]
);

const configuration = vscode.workspace.getConfiguration("path-intellisense");
const rawMappings = configuration["mappings"];
const parsedMappings = parseMappings(rawMappings);

const allMappings = [...parsedMappings, ...mappingsFromWorkspaceConfig];
const mappingsWithCorrectPath = replaceWorkspaceRoot(allMappings, workfolder);

return {
mappings: mappingsWithCorrectPath
};
}

function getWorkspaceConfiguration(): Config {
const cfgExtension = vscode.workspace.getConfiguration("path-intellisense");
const cfgGeneral = vscode.workspace.getConfiguration("files");
const cfgExtension = getConfig("path-intellisense");
const cfgGeneral = getConfig("files");
const mappings = await getMappings(cfgExtension, workspaceFolder);

return {
autoSlash: cfgExtension["autoSlashAfterDirectory"],
showHiddenFiles: cfgExtension["showHiddenFiles"],
withExtension: cfgExtension["extensionOnImport"],
absolutePathToWorkspace: cfgExtension["absolutePathToWorkspace"],
filesExclude: cfgGeneral["exclude"],
mappings: []
mappings,
};
}

/**
* From { "lib": "libraries", "other": "otherpath" }
* To [ { key: "lib", value: "libraries" }, { key: "other", value: "otherpath" } ]
* @param mappings { "lib": "libraries" }
*/
function parseMappings(mappings: { [key: string]: string }): Mapping[] {
return Object.entries(mappings).map(([key, value]) => ({ key, value }));
}

/**
* Replace ${workspaceRoot} with workfolder.uri.path
* @param mappings
* @param workfolder
*/
function replaceWorkspaceRoot(
mappings: Mapping[],
workfolder: vscode.WorkspaceFolder
): Mapping[] {
return mappings.map(mapping => {
return {
key: mapping.key,
value: mapping.value.replace("${workspaceRoot}", workfolder.uri.path)
};
});
}

function createMappingsFromWorkspaceConfig(tsconfig: {
compilerOptions: { baseUrl: string };
}): Mapping[] {
const mappings: Mapping[] = [];
const baseUrl = tsconfig?.compilerOptions?.baseUrl;

if (baseUrl) {
mappings.push({
key: baseUrl,
// value: `${workfolder.uri.path}/${baseUrl}`
value: "${workspaceRoot}/" + baseUrl
});
}

// Todo: paths property

return mappings;
async function getMappings(
configuration: vscode.WorkspaceConfiguration,
workfolder?: vscode.WorkspaceFolder
): Promise<Mapping[]> {
const mappings = parseMappings(configuration["mappings"]);
const tsConfigMappings = await getWorkfolderTsConfigConfiguration(workfolder);
const allMappings = [...mappings, ...tsConfigMappings];
return replaceWorkspaceRoot(allMappings, workfolder);
}
34 changes: 34 additions & 0 deletions src/configuration/mapping.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Mapping } from "./configuration.interface";
import * as vscode from "vscode";

/**
* From { "lib": "libraries", "other": "otherpath" }
* To [ { key: "lib", value: "libraries" }, { key: "other", value: "otherpath" } ]
* @param mappings { "lib": "libraries" }
*/
export function parseMappings(mappings: { [key: string]: string }): Mapping[] {
return Object.entries(mappings).map(([key, value]) => ({ key, value }));
}

/**
* Replace ${workspaceRoot} with workfolder.uri.path
* @param mappings
* @param workfolder
*/
export function replaceWorkspaceRoot(
mappings: Mapping[],
workfolder?: vscode.WorkspaceFolder
): Mapping[] {
const rootPath = workfolder?.uri.path;

if (rootPath) {
return mappings.map(({ key, value }) => ({
key,
value: value.replace("${workspaceRoot}", rootPath),
}));
} else {
return mappings.filter(
({ value }) => value.indexOf("${workspaceRoot}") === -1
);
}
}
85 changes: 85 additions & 0 deletions src/configuration/tsconfig.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import * as vscode from "vscode";
import * as JSON5 from "json5";
import { readFileSync } from "fs";
import { Mapping } from "./configuration.interface";

export const getWorkfolderTsConfigConfiguration = memoize(async function (
workfolder: vscode.WorkspaceFolder
): Promise<Mapping[]> {
const include = new vscode.RelativePattern(workfolder, "[tj]sconfig.json");
const exclude = new vscode.RelativePattern(workfolder, "**/node_modules/**");
const files = await vscode.workspace.findFiles(include, exclude);

return files.reduce((mappings: Mapping[], file) => {
try {
const parsedFile = JSON5.parse(readFileSync(file.fsPath).toString());
const newMappings = createMappingsFromWorkspaceConfig(parsedFile);
return [...mappings, ...newMappings];
} catch (error) {
return mappings;
}
}, []);
});

export function subscribeToTsConfigChanges(): vscode.Disposable[] {
const disposables: vscode.Disposable[] = [];
for (const workfolder of vscode.workspace.workspaceFolders || []) {
const pattern = new vscode.RelativePattern(workfolder, "[tj]sconfig.json");
const fileWatcher = vscode.workspace.createFileSystemWatcher(pattern);
fileWatcher.onDidChange(() => invalidateCache(workfolder));
disposables.push(fileWatcher);
}
return disposables;
}

function createMappingsFromWorkspaceConfig(tsconfig: {
compilerOptions: { baseUrl: string };
}): Mapping[] {
const mappings: Mapping[] = [];
const baseUrl = tsconfig?.compilerOptions?.baseUrl;

if (baseUrl) {
mappings.push({
key: baseUrl,
// value: `${workfolder.uri.path}/${baseUrl}`
value: "${workspaceRoot}/" + baseUrl,
});
}

// Todo: paths property

return mappings;
}

/** Caching */

let cachedMappings = new Map<string, Mapping[]>();

function memoize(
fn: (workfolder: vscode.WorkspaceFolder) => Promise<Mapping[]>
) {
async function cachedFunction(
workfolder?: vscode.WorkspaceFolder
): Promise<Mapping[]> {
if (!workfolder) {
return Promise.resolve([]);
}

const key = workfolder.name;
const cachedMapping = cachedMappings.get(key);

if (cachedMapping) {
return cachedMapping;
} else {
let result = await fn(workfolder);
cachedMappings.set(key, result);
return result;
}
}

return cachedFunction;
}

function invalidateCache(workfolder: vscode.WorkspaceFolder) {
cachedMappings.delete(workfolder.name);
}
10 changes: 4 additions & 6 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,21 @@
// Import the module and reference it with the alias vscode in your code below
import { ExtensionContext, languages } from "vscode";
import { providers } from "./providers";
import { subscribeToConfigurationService } from "./configuration/configuration.service";
import { subscribeToTsConfigChanges } from "./configuration/tsconfig.service";

// this method is called when your extension is activated
// your extension is activated the very first time the command is executed
export function activate(context: ExtensionContext) {
/**
* Subscribe to the configuration service
* Subscribe to the ts config changes
*/
subscribeToConfigurationService().then(configurationServiceSubscription => {
context.subscriptions.push(...configurationServiceSubscription);
});
context.subscriptions.push(...subscribeToTsConfigChanges());

/**
* Register Providers
* Add new providers in src/providers/
* */
providers.forEach(provider => {
providers.forEach((provider) => {
const disposable = languages.registerCompletionItemProvider(
provider.selector,
provider.provider,
Expand Down
8 changes: 4 additions & 4 deletions src/providers/javascript/javascript.provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ export const JavaScriptProvider: PathIntellisenseProvider = {
triggerCharacters: ["/", '"', "'"],
};

function provideCompletionItems(
async function provideCompletionItems(
document: vscode.TextDocument,
position: vscode.Position
): Thenable<vscode.CompletionItem[]> {
): Promise<vscode.CompletionItem[]> {
const context = createContext(document, position);
const config = getConfiguration();
const config = await getConfiguration(document.uri);

return shouldProvide(context, config)
? provide(context, config)
Expand Down Expand Up @@ -74,7 +74,7 @@ async function provide(
context.document.uri.fsPath,
context.fromString,
rootPath,
[]
config.mappings
);

const childrenOfPath = await getChildrenOfPath(path, config);
Expand Down
Loading

0 comments on commit 12f5488

Please sign in to comment.