@@ -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 {
@@ -200,7 +201,9 @@ export async function getGlobalPipePath(): Promise<string> {
200201
201202 for ( let i = 0 ; i < lines . length - 1 ; i ++ ) {
202203 const line = lines [ i ] . trim ( ) ;
203- if ( ! line ) continue ;
204+ if ( ! line ) {
205+ continue ;
206+ }
204207 void ( async ( ) => {
205208 try {
206209 const message = JSON . parse ( line ) as Record < string , unknown > ;
@@ -256,6 +259,96 @@ export async function getGlobalPipePath(): Promise<string> {
256259 } ) ;
257260}
258261
262+ function asRStringLiteral ( value : string ) : string {
263+ return `"${ value . replace ( / \\ / g, '\\\\' ) . replace ( / " / g, '\\"' ) } "` ;
264+ }
265+
266+ function getAttachSessionScriptPath ( pipePath : string ) : string {
267+ if ( pipePath . endsWith ( '.sock' ) ) {
268+ return pipePath . replace ( / \. s o c k $ / , '.R' ) ;
269+ }
270+ const scriptBase = path . basename ( pipePath ) . replace ( / [ ^ a - z A - Z 0 - 9 _ . - ] / g, '_' ) || 'attach_session' ;
271+ return path . join ( tmpDir ( ) , `${ scriptBase } .R` ) ;
272+ }
273+
274+ function buildAttachSessionScript ( pipePath : string , sessPath : string , installSessScriptPath : string ) : string {
275+ return [
276+ 'local({' ,
277+ ` pipe_path <- ${ asRStringLiteral ( pipePath ) } ` ,
278+ ` sess_src <- ${ asRStringLiteral ( sessPath ) } ` ,
279+ ` install_sess_script <- ${ asRStringLiteral ( installSessScriptPath ) } ` ,
280+ ' bundled_version <- tryCatch(read.dcf(file.path(sess_src, "DESCRIPTION"))[1, "Version"], error = function(e) NA_character_)' ,
281+ ' installed_version <- suppressWarnings(tryCatch(as.character(utils::packageVersion("sess")), error = function(e) NA_character_))' ,
282+ ' needs_install <- is.na(installed_version) || (!is.na(bundled_version) && utils::compareVersion(installed_version, bundled_version) < 0)' ,
283+ ' if (needs_install) {' ,
284+ ' if (!file.exists(install_sess_script)) {' ,
285+ ' stop(sprintf("install_sess.R not found: %s", install_sess_script))' ,
286+ ' }' ,
287+ ' Sys.setenv(VSCODE_R_SESS_PKG_PATH = sess_src)' ,
288+ ' on.exit(Sys.unsetenv(c("VSCODE_R_SESS_PKG_PATH", "VSCODE_R_SESS_REPO")), add = TRUE)' ,
289+ ' source(install_sess_script, local = TRUE)' ,
290+ ' }' ,
291+ ' sess::connect(pipe_path = pipe_path)' ,
292+ '})' ,
293+ '' ,
294+ ] . join ( '\n' ) ;
295+ }
296+
297+ export async function getAttachSessionCommand ( ) : Promise < string > {
298+ const pipePath = await getGlobalPipePath ( ) ;
299+ const sessPath = extensionContext . asAbsolutePath ( 'sess' ) . replace ( / \\ / g, '/' ) ;
300+ const installSessScriptPath = extensionContext . asAbsolutePath ( path . join ( 'R' , 'install_sess.R' ) ) . replace ( / \\ / g, '/' ) ;
301+ const scriptPath = getAttachSessionScriptPath ( pipePath ) ;
302+ await fs . writeFile ( scriptPath , buildAttachSessionScript ( pipePath , sessPath , installSessScriptPath ) , { encoding : 'utf-8' } ) ;
303+ attachSessionScriptPath = scriptPath ;
304+
305+ return `source(${ asRStringLiteral ( scriptPath ) } )` ;
306+ }
307+
308+ async function removePathIfExists ( pathLike : string ) : Promise < void > {
309+ try {
310+ if ( await fs . pathExists ( pathLike ) ) {
311+ await fs . remove ( pathLike ) ;
312+ }
313+ } catch ( e ) {
314+ console . warn ( `[session cleanup] Failed to remove ${ pathLike } ` , e ) ;
315+ }
316+ }
317+
318+ export async function shutdownSessionWatcher ( ) : Promise < void > {
319+ const pipePath = globalPipePath ;
320+
321+ for ( const socket of activeConnections ) {
322+ socket . destroy ( ) ;
323+ }
324+ activeConnections . clear ( ) ;
325+ pipeClient = undefined ;
326+ readBuffers . clear ( ) ;
327+
328+ if ( globalSessionServer ) {
329+ await new Promise < void > ( ( resolve ) => {
330+ try {
331+ globalSessionServer ?. close ( ( ) => resolve ( ) ) ;
332+ } catch {
333+ resolve ( ) ;
334+ }
335+ } ) ;
336+ globalSessionServer = undefined ;
337+ }
338+
339+ if ( attachSessionScriptPath ) {
340+ await removePathIfExists ( attachSessionScriptPath ) ;
341+ attachSessionScriptPath = undefined ;
342+ }
343+
344+ if ( pipePath && pipePath . endsWith ( '.sock' ) ) {
345+ await removePathIfExists ( pipePath ) ;
346+ await removePathIfExists ( pipePath . replace ( / \. s o c k $ / , '.R' ) ) ;
347+ }
348+
349+ globalPipePath = undefined ;
350+ }
351+
259352export async function activateRSession ( ) : Promise < void > {
260353 if ( config ( ) . get < boolean > ( 'sessionWatcher' ) ) {
261354 console . info ( '[activateRSession]' ) ;
@@ -284,6 +377,30 @@ export async function activateRSession(): Promise<void> {
284377 }
285378 }
286379
380+ if ( config ( ) . get < boolean > ( 'alwaysUseActiveTerminal' ) ) {
381+ if ( terminal ) {
382+ const command = await getAttachSessionCommand ( ) ;
383+ terminal . sendText ( command , true ) ;
384+ terminal . show ( ) ;
385+ return ;
386+ }
387+
388+ const action = await window . showInformationMessage (
389+ 'No active terminal is available. You can copy the attach command or create a managed R terminal.' ,
390+ 'Copy Attach Command' ,
391+ 'Create R Terminal'
392+ ) ;
393+
394+ if ( action === 'Copy Attach Command' ) {
395+ await connectToSession ( ) ;
396+ return ;
397+ }
398+ if ( action === 'Create R Terminal' ) {
399+ await rTerminal . createRTerm ( ) ;
400+ }
401+ return ;
402+ }
403+
287404 console . info ( '[activateRSession] Creating new R terminal' ) ;
288405 await rTerminal . createRTerm ( ) ;
289406 } else {
@@ -1008,8 +1125,7 @@ export async function sessionRequest(data: Record<string, unknown>): Promise<unk
10081125}
10091126
10101127export async function connectToSession ( ) : Promise < void > {
1011- const pipePath = await getGlobalPipePath ( ) ;
1012- const command = `sess::connect(pipe_path="${ pipePath . replace ( / \\ / g, '\\\\' ) } ")` ;
1128+ const command = await getAttachSessionCommand ( ) ;
10131129 void vscode . env . clipboard . writeText ( command ) ;
10141130 void vscode . window . showInformationMessage ( `R command copied to clipboard: ${ command } ` ) ;
10151131}
0 commit comments