@@ -13,7 +13,7 @@ import { config, readContent, setContext, UriIcon } from './util';
1313import * as rTerminal from './rTerminal' ;
1414import { purgeAddinPickerItems , RSEditOperation , RSRange } from './rstudioapi' ;
1515
16- import { homeExtDir , rWorkspace , globalRHelp , globalPlotManager , sessionStatusBarItem } from './extension' ;
16+ import { extensionContext , homeExtDir , rWorkspace , globalRHelp , globalPlotManager , sessionStatusBarItem , tmpDir } from './extension' ;
1717
1818import { showWebView } from './webViewer' ;
1919
@@ -114,6 +114,7 @@ const pendingRequests = new Map<number, { resolve: (value: unknown) => void, rej
114114const readBuffers = new Map < IpcSocket , string > ( ) ;
115115
116116let globalSessionServer : net . Server | undefined ;
117+ let attachSessionScriptPath : string | undefined ;
117118
118119function isPidRunning ( pid : number ) : boolean {
119120 try {
@@ -256,6 +257,103 @@ export async function getGlobalPipePath(): Promise<string> {
256257 } ) ;
257258}
258259
260+ function asRStringLiteral ( value : string ) : string {
261+ return `"${ value . replace ( / \\ / g, '\\\\' ) . replace ( / " / g, '\\"' ) } "` ;
262+ }
263+
264+ function getAttachSessionScriptPath ( pipePath : string ) : string {
265+ if ( pipePath . endsWith ( '.sock' ) ) {
266+ return pipePath . replace ( / \. s o c k $ / , '.R' ) ;
267+ }
268+ const scriptBase = path . basename ( pipePath ) . replace ( / [ ^ a - z A - Z 0 - 9 _ . - ] / g, '_' ) || 'attach_session' ;
269+ return path . join ( tmpDir ( ) , `${ scriptBase } .R` ) ;
270+ }
271+
272+ function buildAttachSessionScript ( pipePath : string , sessPath : string ) : string {
273+ return [
274+ 'local({' ,
275+ ` pipe_path <- ${ asRStringLiteral ( pipePath ) } ` ,
276+ ` sess_src <- ${ asRStringLiteral ( sessPath ) } ` ,
277+ ' bundled_version <- tryCatch(read.dcf(file.path(sess_src, "DESCRIPTION"))[1, "Version"], error = function(e) NA_character_)' ,
278+ ' installed_version <- suppressWarnings(tryCatch(as.character(utils::packageVersion("sess")), error = function(e) NA_character_))' ,
279+ ' needs_install <- is.na(installed_version) || (!is.na(bundled_version) && utils::compareVersion(installed_version, bundled_version) < 0)' ,
280+ ' if (needs_install) {' ,
281+ ' lib_candidates <- unique(c(.libPaths(), Sys.getenv("R_LIBS_USER", unset = "")))' ,
282+ ' lib_candidates <- lib_candidates[nzchar(lib_candidates)]' ,
283+ ' writable <- lib_candidates[vapply(lib_candidates, function(lib) {' ,
284+ ' if (!dir.exists(lib)) {' ,
285+ ' dir.create(lib, recursive = TRUE, showWarnings = FALSE)' ,
286+ ' }' ,
287+ ' file.access(lib, 2) == 0' ,
288+ ' }, logical(1))]' ,
289+ ' if (length(writable) < 1) {' ,
290+ ' message("[vscode-R] Could not find a writable library path for this terminal.")' ,
291+ ' message("[vscode-R] Install the bundled sess package manually:")' ,
292+ ' message(sprintf("install.packages(%s, repos = NULL, type = \\"source\\")", shQuote(sess_src)))' ,
293+ ' stop("No writable R library path available for installing sess")' ,
294+ ' }' ,
295+ ' install.packages(sess_src, repos = NULL, type = "source", lib = writable[[1]])' ,
296+ ' }' ,
297+ ' sess::connect(pipe_path = pipe_path)' ,
298+ '})' ,
299+ '' ,
300+ ] . join ( '\n' ) ;
301+ }
302+
303+ export async function getAttachSessionCommand ( ) : Promise < string > {
304+ const pipePath = await getGlobalPipePath ( ) ;
305+ const sessPath = extensionContext . asAbsolutePath ( 'sess' ) . replace ( / \\ / g, '/' ) ;
306+ const scriptPath = getAttachSessionScriptPath ( pipePath ) ;
307+ await fs . writeFile ( scriptPath , buildAttachSessionScript ( pipePath , sessPath ) , { encoding : 'utf-8' } ) ;
308+ attachSessionScriptPath = scriptPath ;
309+
310+ return `source(${ asRStringLiteral ( scriptPath ) } )` ;
311+ }
312+
313+ async function removePathIfExists ( pathLike : string ) : Promise < void > {
314+ try {
315+ if ( await fs . pathExists ( pathLike ) ) {
316+ await fs . remove ( pathLike ) ;
317+ }
318+ } catch ( e ) {
319+ console . warn ( `[session cleanup] Failed to remove ${ pathLike } ` , e ) ;
320+ }
321+ }
322+
323+ export async function shutdownSessionWatcher ( ) : Promise < void > {
324+ const pipePath = globalPipePath ;
325+
326+ for ( const socket of activeConnections ) {
327+ socket . destroy ( ) ;
328+ }
329+ activeConnections . clear ( ) ;
330+ pipeClient = undefined ;
331+ readBuffers . clear ( ) ;
332+
333+ if ( globalSessionServer ) {
334+ await new Promise < void > ( ( resolve ) => {
335+ try {
336+ globalSessionServer ?. close ( ( ) => resolve ( ) ) ;
337+ } catch {
338+ resolve ( ) ;
339+ }
340+ } ) ;
341+ globalSessionServer = undefined ;
342+ }
343+
344+ if ( attachSessionScriptPath ) {
345+ await removePathIfExists ( attachSessionScriptPath ) ;
346+ attachSessionScriptPath = undefined ;
347+ }
348+
349+ if ( pipePath && pipePath . endsWith ( '.sock' ) ) {
350+ await removePathIfExists ( pipePath ) ;
351+ await removePathIfExists ( pipePath . replace ( / \. s o c k $ / , '.R' ) ) ;
352+ }
353+
354+ globalPipePath = undefined ;
355+ }
356+
259357export async function activateRSession ( ) : Promise < void > {
260358 if ( config ( ) . get < boolean > ( 'sessionWatcher' ) ) {
261359 console . info ( '[activateRSession]' ) ;
@@ -284,6 +382,30 @@ export async function activateRSession(): Promise<void> {
284382 }
285383 }
286384
385+ if ( config ( ) . get < boolean > ( 'alwaysUseActiveTerminal' ) ) {
386+ if ( terminal ) {
387+ const command = await getAttachSessionCommand ( ) ;
388+ terminal . sendText ( command , true ) ;
389+ terminal . show ( ) ;
390+ return ;
391+ }
392+
393+ const action = await window . showInformationMessage (
394+ 'No active terminal is available. You can copy the attach command or create a managed R terminal.' ,
395+ 'Copy Attach Command' ,
396+ 'Create R Terminal'
397+ ) ;
398+
399+ if ( action === 'Copy Attach Command' ) {
400+ await connectToSession ( ) ;
401+ return ;
402+ }
403+ if ( action === 'Create R Terminal' ) {
404+ await rTerminal . createRTerm ( ) ;
405+ }
406+ return ;
407+ }
408+
287409 console . info ( '[activateRSession] Creating new R terminal' ) ;
288410 await rTerminal . createRTerm ( ) ;
289411 } else {
@@ -1008,8 +1130,7 @@ export async function sessionRequest(data: Record<string, unknown>): Promise<unk
10081130}
10091131
10101132export async function connectToSession ( ) : Promise < void > {
1011- const pipePath = await getGlobalPipePath ( ) ;
1012- const command = `sess::connect(pipe_path="${ pipePath . replace ( / \\ / g, '\\\\' ) } ")` ;
1133+ const command = await getAttachSessionCommand ( ) ;
10131134 void vscode . env . clipboard . writeText ( command ) ;
10141135 void vscode . window . showInformationMessage ( `R command copied to clipboard: ${ command } ` ) ;
10151136}
0 commit comments