From 11144a20b4172f85926a859be512494f3e390bee Mon Sep 17 00:00:00 2001 From: seem Date: Mon, 11 Sep 2023 18:25:26 +0200 Subject: [PATCH 1/5] update R startup heuristic Only start an R runtime immediately if the workspace contains R files, or if it's empty but the user has installed RStudio. This is less likely to incorrectly start R in Python projects for users that happen to have RStudio installed. See https://github.com/posit-dev/positron/issues/1282#issuecomment-1714179194 for more detail. --- extensions/positron-r/src/provider.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/extensions/positron-r/src/provider.ts b/extensions/positron-r/src/provider.ts index 83e1b630273..c9b1537e67a 100644 --- a/extensions/positron-r/src/provider.ts +++ b/extensions/positron-r/src/provider.ts @@ -103,10 +103,13 @@ export async function* rRuntimeProvider(context: vscode.ExtensionContext): Async return semver.compare(b.semVersion, a.semVersion) || a.arch.localeCompare(b.arch); }); - // For now, we recommend R for the workspace if the user is an RStudio user. + // For now, we recommend R for the workspace if the workspace contains R files, + // or if it's empty and the user is an RStudio user. // In the future, we will use more sophisticated heuristics, such as // checking an renv lockfile for a match against a system version of R. - let recommendedForWorkspace = isRStudioUser(); + const hasRFiles = async () => hasFiles('**/*.r'); + const isEmpty = async () => !(await hasFiles('**/*')); + let recommendedForWorkspace = await hasRFiles() || (await isEmpty() && isRStudioUser()); // Loop over the R installations and create a language runtime for each one. // @@ -295,6 +298,12 @@ function binFragment(version: string): string { } } +// Check if the current workspace contains files matching a glob pattern +async function hasFiles(glob: string): Promise { + // Exclude node_modules for performance reasons + return (await vscode.workspace.findFiles(glob, '**/node_modules/**', 1)).length > 0; +} + /** * Attempts to heuristically determine if the user is an RStudio user by * checking for recently modified files in RStudio's state directory. From 85da650ed1b34a6b5a1d1accb7e212bf7a97ed44 Mon Sep 17 00:00:00 2001 From: seem Date: Mon, 11 Sep 2023 18:35:40 +0200 Subject: [PATCH 2/5] fix casing --- extensions/positron-r/src/provider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/positron-r/src/provider.ts b/extensions/positron-r/src/provider.ts index c9b1537e67a..e61985164f2 100644 --- a/extensions/positron-r/src/provider.ts +++ b/extensions/positron-r/src/provider.ts @@ -107,7 +107,7 @@ export async function* rRuntimeProvider(context: vscode.ExtensionContext): Async // or if it's empty and the user is an RStudio user. // In the future, we will use more sophisticated heuristics, such as // checking an renv lockfile for a match against a system version of R. - const hasRFiles = async () => hasFiles('**/*.r'); + const hasRFiles = async () => hasFiles('**/*.R'); const isEmpty = async () => !(await hasFiles('**/*')); let recommendedForWorkspace = await hasRFiles() || (await isEmpty() && isRStudioUser()); From a725cefd008fd0d8e4c747cae8700fb41d12b82f Mon Sep 17 00:00:00 2001 From: seem Date: Tue, 12 Sep 2023 17:50:35 +0200 Subject: [PATCH 3/5] add more R files and qmd files with R code blocks to the R startup heuristic --- extensions/positron-r/src/provider.ts | 58 +++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/extensions/positron-r/src/provider.ts b/extensions/positron-r/src/provider.ts index e61985164f2..a7a12baf1f8 100644 --- a/extensions/positron-r/src/provider.ts +++ b/extensions/positron-r/src/provider.ts @@ -103,13 +103,11 @@ export async function* rRuntimeProvider(context: vscode.ExtensionContext): Async return semver.compare(b.semVersion, a.semVersion) || a.arch.localeCompare(b.arch); }); - // For now, we recommend R for the workspace if the workspace contains R files, - // or if it's empty and the user is an RStudio user. + // For now, we recommend the first R runtime for the workspace based on a set of + // non-runtime-specific heuristics. // In the future, we will use more sophisticated heuristics, such as // checking an renv lockfile for a match against a system version of R. - const hasRFiles = async () => hasFiles('**/*.R'); - const isEmpty = async () => !(await hasFiles('**/*')); - let recommendedForWorkspace = await hasRFiles() || (await isEmpty() && isRStudioUser()); + let recommendedForWorkspace = await shouldRecommendForWorkspace(); // Loop over the R installations and create a language runtime for each one. // @@ -298,10 +296,54 @@ function binFragment(version: string): string { } } -// Check if the current workspace contains files matching a glob pattern -async function hasFiles(glob: string): Promise { +// Should we recommend an R runtime for the workspace? +async function shouldRecommendForWorkspace(): Promise { + // Check if the workspace contains R-related files. + const globs = [ + '**/*.R', + '**/*.Rmd', + '**/.Rprofile', + '**/renv.lock', + '**/.Rbuildignore', + '**/*.Rproj' + ]; + // Convert to the glob format used by vscode.workspace.findFiles. + const glob = `{${globs.join(',')}}`; + if (await hasFiles(glob)) { + return true; + } + + // Check if the workspace contains .qmd files with R code blocks. + // Limit to 1000 files (unordered) - the assumption is that it's unlikely that none will contain + // an R code block in an R workspace. + const qmdFiles = await findFiles('**/*.qmd', 1000); + if (qmdFiles) { + for (const qmdFile of qmdFiles) { + const fileContents = (await vscode.workspace.fs.readFile(qmdFile)).toString(); + // Check if the file contains an R code block. + if (fileContents.match(/^```(\{\.?r.*\}|\.?r)/m)) { + return true; + } + } + } + + // Check if the workspace is empty and the user is an RStudio user. + if (!(await hasFiles('**/*')) && isRStudioUser()) { + return true; + } + + return false; +} + +// Find files in the current workspace matching a glob pattern. +async function findFiles(glob: string, maxResult: number): Promise { // Exclude node_modules for performance reasons - return (await vscode.workspace.findFiles(glob, '**/node_modules/**', 1)).length > 0; + return vscode.workspace.findFiles(glob, '**/node_modules/**', maxResult); +} + +// Check if the current workspace contains files matching a glob pattern. +async function hasFiles(glob: string): Promise { + return (await findFiles(glob, 1)).length > 0; } /** From c4e055552c87b0afe490e48522fa9ef2ba899422 Mon Sep 17 00:00:00 2001 From: seem Date: Tue, 12 Sep 2023 17:53:29 +0200 Subject: [PATCH 4/5] revert qmd R startup heuristic --- extensions/positron-r/src/provider.ts | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/extensions/positron-r/src/provider.ts b/extensions/positron-r/src/provider.ts index a7a12baf1f8..f5303d734d6 100644 --- a/extensions/positron-r/src/provider.ts +++ b/extensions/positron-r/src/provider.ts @@ -313,20 +313,6 @@ async function shouldRecommendForWorkspace(): Promise { return true; } - // Check if the workspace contains .qmd files with R code blocks. - // Limit to 1000 files (unordered) - the assumption is that it's unlikely that none will contain - // an R code block in an R workspace. - const qmdFiles = await findFiles('**/*.qmd', 1000); - if (qmdFiles) { - for (const qmdFile of qmdFiles) { - const fileContents = (await vscode.workspace.fs.readFile(qmdFile)).toString(); - // Check if the file contains an R code block. - if (fileContents.match(/^```(\{\.?r.*\}|\.?r)/m)) { - return true; - } - } - } - // Check if the workspace is empty and the user is an RStudio user. if (!(await hasFiles('**/*')) && isRStudioUser()) { return true; @@ -335,15 +321,10 @@ async function shouldRecommendForWorkspace(): Promise { return false; } -// Find files in the current workspace matching a glob pattern. -async function findFiles(glob: string, maxResult: number): Promise { - // Exclude node_modules for performance reasons - return vscode.workspace.findFiles(glob, '**/node_modules/**', maxResult); -} - // Check if the current workspace contains files matching a glob pattern. async function hasFiles(glob: string): Promise { - return (await findFiles(glob, 1)).length > 0; + // Exclude node_modules for performance reasons + return (await vscode.workspace.findFiles(glob, '**/node_modules/**', 1)).length > 0; } /** From e93110d058bdfc9b9fd36edb7a76102418f4a4cc Mon Sep 17 00:00:00 2001 From: Wasim Lorgat Date: Tue, 12 Sep 2023 19:10:50 +0200 Subject: [PATCH 5/5] Update extensions/positron-r/src/provider.ts Co-authored-by: Julia Silge --- extensions/positron-r/src/provider.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/positron-r/src/provider.ts b/extensions/positron-r/src/provider.ts index f5303d734d6..d97bcf93ce4 100644 --- a/extensions/positron-r/src/provider.ts +++ b/extensions/positron-r/src/provider.ts @@ -305,6 +305,7 @@ async function shouldRecommendForWorkspace(): Promise { '**/.Rprofile', '**/renv.lock', '**/.Rbuildignore', + '**/.Renviron', '**/*.Rproj' ]; // Convert to the glob format used by vscode.workspace.findFiles.