Skip to content

Commit

Permalink
Glob for all project roots in the workspace
Browse files Browse the repository at this point in the history
Let the user pick if there are more than one
Changes the way we find the closest project root
Not using node fs module at all
Fixes #1254
  • Loading branch information
PEZ committed Mar 20, 2022
1 parent 2e877dd commit 4ed9d80
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 84 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Changes to Calva.

## [Unreleased]
- Maintenance: [Update _even more_ TypeScript code to be compatible with strictNullChecks.](https://github.com/BetterThanTomorrow/calva/pull/1605)
- [Support Polylith and monorepo jack-in/connect better](https://github.com/BetterThanTomorrow/calva/issues/1254)

## [2.0.256] - 2022-03-19
- Be more graceful about that [clojure-lsp does not start in the Getting Started REPL](https://github.com/BetterThanTomorrow/calva/issues/1601)
Expand Down
37 changes: 37 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,43 @@
}
}
},
"calva.projectRootsSearchExclude": {
"type": "array",
"markdownDescription": "",
"items": {
"type": "string"
},
"default": [
".idea",
".vscode",
".ensime_cache",
".eunit",
".git",
".hg",
".fslckout",
"_FOSSIL_",
".bzr",
"_darcs",
".pijul",
".tox",
".svn",
".stack-work",
".ccls-cache",
".cache",
".clangd",
"node_modules",
"flow-typed",
"bower_components",
".cpcache",
".classpath",
".project",
"target",
".shadow-cljs",
".lsp",
".clj-kondo",
"*.code-search"
]
},
"calva.enableJSCompletions": {
"type": "boolean",
"description": "Should Calva use suitible and bring you JavaScript completions? This is an experimental cider-nrepl feature. Disable if completions start to throw errors.",
Expand Down
1 change: 1 addition & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ function getConfig() {
useDeprecatedAliasFlag: configOptions.get<boolean>('jackIn.useDeprecatedAliasFlag'),
},
enableClojureLspOnStart: configOptions.get<boolean>('enableClojureLspOnStart'),
projectRootsSearchExclude: configOptions.get<string[]>('projectRootsSearchExclude')
};
}

Expand Down
134 changes: 50 additions & 84 deletions src/state.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as vscode from 'vscode';
import Analytics from './analytics';
import * as util from './utilities';
import * as config from './config';
import * as path from 'path';
import * as os from 'os';
import * as fs from 'fs';
Expand Down Expand Up @@ -134,100 +135,65 @@ function getProjectWsFolder(): vscode.WorkspaceFolder | undefined {
}

/**
* Figures out, and stores, the current clojure project root
* Also stores the WorkSpace folder for the project.
*
* 1. If there is no file open in single-rooted workspace use
* the workspace folder as a starting point. In multi-rooted
* workspaces stop and complain.
* 2. If there is a file open, use it to determine the project root
* by looking for project files from the file's directory and up to
* the window root (for plain folder windows) or the file's
* workspace folder root (for workspaces) to find the project root.
* Figures out, and stores, the current clojure project root in Calva state
*/
export async function initProjectDir(uri?: vscode.Uri): Promise<void> {
if (uri) {
setStateValue(PROJECT_DIR_KEY, path.resolve(uri.fsPath));
setStateValue(PROJECT_DIR_URI_KEY, uri);
} else {
const projectFileNames: string[] = ['project.clj', 'shadow-cljs.edn', 'deps.edn'];
const doc = util.tryToGetDocument({});
const workspaceFolder = getProjectWsFolder();
findLocalProjectRoot(projectFileNames, doc, workspaceFolder);
await findProjectRootUri(projectFileNames, doc, workspaceFolder);
}
}

function findLocalProjectRoot(
projectFileNames: string[],
doc: vscode.TextDocument | undefined,
workspaceFolder: vscode.WorkspaceFolder | undefined
): undefined {
if (workspaceFolder) {
let rootPath: string = path.resolve(workspaceFolder.uri.fsPath);
setStateValue(PROJECT_DIR_KEY, rootPath);
setStateValue(PROJECT_DIR_URI_KEY, workspaceFolder.uri);
const docPath = doc && path.dirname(doc.uri.fsPath);

let currentPath =
util.isDefined(docPath) && docPath !== '.' ? docPath : workspaceFolder.uri.fsPath;

let previousPath = '';

do {
for (const projectFile of projectFileNames) {
const fullPath = path.resolve(currentPath, projectFile);
if (fs.existsSync(fullPath)) {
rootPath = currentPath;
break;
}
}
if (currentPath === rootPath) {
break;
}
previousPath = currentPath;
currentPath = path.resolve(currentPath, '..');
} while (currentPath !== previousPath);

// at least be sure the the root folder contains a
// supported project.
for (const projectFile of projectFileNames) {
const fullPath = path.resolve(rootPath, projectFile);
if (fs.existsSync(fullPath)) {
setStateValue(PROJECT_DIR_KEY, rootPath);
setStateValue(PROJECT_DIR_URI_KEY, vscode.Uri.file(rootPath));
return;
}
}
}
return;
}

async function findProjectRootUri(projectFileNames, doc, workspaceFolder): Promise<void> {
let searchUri = doc?.uri || workspaceFolder?.uri;
if (searchUri && !(searchUri.scheme === 'untitled')) {
let prev = null;
while (searchUri != prev) {
try {
for (const projectFile in projectFileNames) {
const u = vscode.Uri.joinPath(searchUri, projectFileNames[projectFile]);
try {
await vscode.workspace.fs.stat(u);
setStateValue(PROJECT_DIR_URI_KEY, searchUri);
return;
} catch {
// continue regardless of error
}
}
} catch (e) {
console.error(`Problems in search for project root directory: ${e}`);
}
prev = searchUri;
searchUri = vscode.Uri.joinPath(searchUri, '..');
}
console.log(workspaceFolder);
const candidatePaths = await findProjectRootPaths();
const closestRootPath = findClosestProjectRootPath(candidatePaths);
const projectRootPath = await pickProjectRootPath(candidatePaths, closestRootPath);
setStateValue(PROJECT_DIR_KEY, projectRootPath);
setStateValue(PROJECT_DIR_URI_KEY, vscode.Uri.file(projectRootPath));
}
}

async function findProjectRootPaths() {
const projectFileNames: string[] = ['project.clj', 'shadow-cljs.edn', 'deps.edn'];
const projectFilesGlob = `**/{${projectFileNames.join(',')}}`;
const excludeDirsGlob = `**/{${config.getConfig().projectRootsSearchExclude.join(',')}}`;
const t0 = new Date().getTime();
const candidateUris = await vscode.workspace.findFiles(projectFilesGlob, excludeDirsGlob, 10000);
console.debug('glob took', new Date().getTime() - t0, 'ms');
const projectFilePaths = candidateUris.map((uri) => path.dirname(uri.fsPath));
const candidatePaths = [...new Set(projectFilePaths)];
console.log(candidatePaths);
return candidatePaths;
}

function findClosestProjectRootPath(candidatePaths: string[]) {
const doc = util.tryToGetDocument({});
console.log(doc);
const docDir = doc && doc.uri ? path.dirname(doc.uri.fsPath) : undefined;
const closestRootPath = docDir
? candidatePaths
.filter((p) => docDir.startsWith(p))
.sort()
.reverse()[0]
: candidatePaths[0];
console.log(closestRootPath);
return closestRootPath;
}

async function pickProjectRootPath(candidatePaths: string[], closestRootPath: string) {
const pickedRootPath = candidatePaths.length < 2
? undefined
: await util.quickPickSingle({
values: candidatePaths,
// title: "Project root",
// default: closestRootPath
placeHolder: 'Select project root',
saveAs: `projectRoot`,
autoSelect: true,
});
const projectRootPath = typeof pickedRootPath === 'string' ? pickedRootPath : closestRootPath;
return projectRootPath;
}

/**
*
* Tries to resolve absolute path in relation to project root
Expand Down

0 comments on commit 4ed9d80

Please sign in to comment.