1+ import * as path from "path" ;
12import * as vscode from "vscode" ;
23import { ActiveJsTsEditorTracker } from "./activeJsTsEditorTracker" ;
34import { Client } from "./client" ;
@@ -6,7 +7,12 @@ import { ManagedFileContextManager } from "./managedFileContext";
67import { ProjectStatus } from "./projectStatus" ;
78import { setupStatusBar } from "./statusBar" ;
89import { TelemetryReporter } from "./telemetryReporting" ;
9- import { getExe } from "./util" ;
10+ import {
11+ getBuiltinExePath ,
12+ getExe ,
13+ resolveTsdkPathToExe ,
14+ useWorkspaceTsdkStorageKey ,
15+ } from "./util" ;
1016
1117/**
1218 * SessionManager's lifetime is equal to that of the extension. It is responsible
@@ -55,7 +61,7 @@ export class SessionManager implements vscode.Disposable {
5561 this . outputChannel . appendLine ( "Restarting TypeScript Native Preview..." ) ;
5662 await this . currentSession . dispose ( ) ;
5763 }
58- this . currentSession = new Session ( this . outputChannel , this . traceOutputChannel , this . initializedEventEmitter , this . telemetryReporter ) ;
64+ this . currentSession = new Session ( context , this . outputChannel , this . traceOutputChannel , this . initializedEventEmitter , this . telemetryReporter ) ;
5965 return this . currentSession . start ( context ) ;
6066 }
6167
@@ -91,19 +97,22 @@ export class SessionManager implements vscode.Disposable {
9197class Session implements vscode . Disposable {
9298 client : Client ;
9399 private disposables : vscode . Disposable [ ] = [ ] ;
100+ private context : vscode . ExtensionContext ;
94101 private outputChannel : vscode . LogOutputChannel ;
95102 private traceOutputChannel : vscode . LogOutputChannel ;
96103 private telemetryReporter : TelemetryReporter ;
97104 private initializedEventEmitter : vscode . EventEmitter < void > ;
98105
99106 constructor (
107+ context : vscode . ExtensionContext ,
100108 outputChannel : vscode . LogOutputChannel ,
101109 traceOutputChannel : vscode . LogOutputChannel ,
102110 initializedEventEmitter : vscode . EventEmitter < void > ,
103111 telemetryReporter : TelemetryReporter ,
104112 ) {
105113 this . client = new Client ( outputChannel , traceOutputChannel , initializedEventEmitter , telemetryReporter ) ;
106114 this . disposables . push ( this . client ) ;
115+ this . context = context ;
107116 this . outputChannel = outputChannel ;
108117 this . traceOutputChannel = traceOutputChannel ;
109118 this . telemetryReporter = telemetryReporter ;
@@ -149,10 +158,13 @@ class Session implements vscode.Disposable {
149158 this . traceOutputChannel . show ( ) ;
150159 } ) ) ;
151160
152- this . disposables . push ( vscode . commands . registerCommand ( "typescript.native-preview.selectVersion" , async ( ) => {
161+ this . disposables . push ( vscode . commands . registerCommand ( "typescript.selectTypeScriptVersion" , async ( ) => {
162+ await promptSelectVersion ( this . context , this . client , this . outputChannel ) ;
153163 } ) ) ;
154164
155- this . disposables . push ( vscode . commands . registerCommand ( "typescript.native-preview.showMenu" , showCommands ) ) ;
165+ this . disposables . push ( vscode . commands . registerCommand ( "typescript.native-preview.showMenu" , ( ) => {
166+ showCommands ( this . client ) ;
167+ } ) ) ;
156168
157169 this . disposables . push ( vscode . commands . registerCommand ( "typescript.native-preview.reportIssue" , ( ) => {
158170 this . telemetryReporter . sendTelemetryEvent ( "command.reportIssue" ) ;
@@ -247,8 +259,15 @@ class Session implements vscode.Disposable {
247259 }
248260}
249261
250- async function showCommands ( ) : Promise < void > {
251- const commands : readonly { label : string ; description : string ; command : string ; } [ ] = [
262+ async function showCommands ( client : Client ) : Promise < void > {
263+ interface CommandItem {
264+ label : string ;
265+ description ?: string ;
266+ kind ?: vscode . QuickPickItemKind ;
267+ command ?: string ;
268+ action ?: ( ) => Promise < void > ;
269+ }
270+ const commands : CommandItem [ ] = [
252271 {
253272 label : "$(refresh) Restart Server" ,
254273 description : "Restart the TypeScript Native Preview language server" ,
@@ -269,19 +288,212 @@ async function showCommands(): Promise<void> {
269288 description : "Report an issue with TypeScript Native Preview" ,
270289 command : "typescript.native-preview.reportIssue" ,
271290 } ,
291+ {
292+ label : "$(versions) Select Version" ,
293+ description : "Choose between bundled and workspace versions" ,
294+ command : "typescript.selectTypeScriptVersion" ,
295+ } ,
272296 {
273297 label : "$(stop-circle) Disable TypeScript Native Preview" ,
274298 description : "Switch back to the built-in TypeScript extension" ,
275299 command : "typescript.native-preview.disable" ,
276300 } ,
277301 ] ;
278302
303+ const showDebugInfo = vscode . workspace . getConfiguration ( "typescript.native-preview" ) . get < boolean > ( "showDebugInfo" , false ) ;
304+ if ( showDebugInfo ) {
305+ const exe = client . getCurrentExe ( ) ;
306+ const pid = client . serverPid ;
307+ commands . push ( { label : "" , kind : vscode . QuickPickItemKind . Separator } ) ;
308+ if ( exe ) {
309+ commands . push ( {
310+ label : `Executable` ,
311+ description : exe . path ,
312+ action : async ( ) => {
313+ await vscode . env . clipboard . writeText ( exe . path ) ;
314+ vscode . window . showInformationMessage ( "Executable path copied to clipboard." ) ;
315+ } ,
316+ } ) ;
317+ }
318+ if ( pid ) {
319+ commands . push ( {
320+ label : `PID` ,
321+ description : `${ pid } ` ,
322+ action : async ( ) => {
323+ await vscode . env . clipboard . writeText ( `${ pid } ` ) ;
324+ vscode . window . showInformationMessage ( "Server PID copied to clipboard." ) ;
325+ } ,
326+ } ) ;
327+ }
328+ }
329+
279330 const selected = await vscode . window . showQuickPick ( commands , {
280331 placeHolder : "TypeScript Native Preview Commands" ,
281332 } ) ;
282333
283334 if ( selected ) {
284- await vscode . commands . executeCommand ( selected . command ) ;
335+ if ( selected . action ) {
336+ await selected . action ( ) ;
337+ }
338+ else if ( selected . command ) {
339+ await vscode . commands . executeCommand ( selected . command ) ;
340+ }
341+ }
342+ }
343+
344+ interface VersionQuickPickItem extends vscode . QuickPickItem {
345+ run ( ) : Promise < void > ;
346+ }
347+
348+ interface DetectedVersion {
349+ label : string ;
350+ version : string ;
351+ tsdkPath : string ;
352+ exePath : string ;
353+ }
354+
355+ async function findWorkspaceNativePreviewPackages ( ) : Promise < DetectedVersion [ ] > {
356+ const results : DetectedVersion [ ] = [ ] ;
357+ for ( const folder of vscode . workspace . workspaceFolders ?? [ ] ) {
358+ const packagePath = vscode . Uri . joinPath ( folder . uri , "node_modules" , "@typescript" , "native-preview" ) ;
359+ const resolved = await resolveTsdkPathToExe ( path . normalize ( packagePath . fsPath ) ) ;
360+ if ( ! resolved ) continue ;
361+ results . push ( {
362+ label : folder . name ,
363+ version : resolved ?. version ?? "unknown" ,
364+ tsdkPath : path . normalize ( packagePath . fsPath ) ,
365+ exePath : resolved ?. path ?? "" ,
366+ } ) ;
367+ }
368+ return results ;
369+ }
370+
371+ async function promptSelectVersion ( context : vscode . ExtensionContext , client : Client , outputChannel : vscode . LogOutputChannel ) : Promise < void > {
372+ const config = vscode . workspace . getConfiguration ( "typescript.native-preview" ) ;
373+ const currentExePath = client . getCurrentExe ( ) ?. path ;
374+ const builtinExe = await getBuiltinExePath ( context ) ;
375+ const workspaceVersions = await findWorkspaceNativePreviewPackages ( ) ;
376+ const bundledVersion = context . extension . packageJSON . version as string ;
377+ const items : VersionQuickPickItem [ ] = [ ] ;
378+
379+ // Bundled version
380+ items . push ( {
381+ label : ( currentExePath === builtinExe . path ? "• " : "" ) + "Use Bundled Version" ,
382+ description : bundledVersion ,
383+ detail : builtinExe . path ,
384+ run : async ( ) => {
385+ await context . workspaceState . update ( useWorkspaceTsdkStorageKey , false ) ;
386+ await config . update ( "tsdk" , undefined , vscode . ConfigurationTarget . Workspace ) ;
387+ outputChannel . appendLine ( "Switched to bundled tsgo version." ) ;
388+ } ,
389+ } ) ;
390+
391+ // Workspace versions
392+ if ( vscode . workspace . isTrusted ) {
393+ for ( const wsVersion of workspaceVersions ) {
394+ const isActive = currentExePath === wsVersion . tsdkPath ;
395+ items . push ( {
396+ label : ( isActive ? "• " : "" ) + "Use Workspace Version" ,
397+ description : wsVersion . version ,
398+ detail : wsVersion . tsdkPath ,
399+ run : async ( ) => {
400+ await context . workspaceState . update ( useWorkspaceTsdkStorageKey , true ) ;
401+ await config . update ( "tsdk" , wsVersion . tsdkPath , vscode . ConfigurationTarget . Workspace ) ;
402+ outputChannel . appendLine ( `Switched to workspace tsgo version (${ wsVersion . version } ).` ) ;
403+ } ,
404+ } ) ;
405+ }
406+ }
407+ else if ( workspaceVersions . length > 0 ) {
408+ items . push ( {
409+ label : "" ,
410+ kind : vscode . QuickPickItemKind . Separator ,
411+ run : async ( ) => { } ,
412+ } ) ;
413+ items . push ( {
414+ label : "$(lock) Manage Workspace Trust to select a workspace version" ,
415+ run : async ( ) => {
416+ await vscode . commands . executeCommand ( "workbench.trust.manage" ) ;
417+ } ,
418+ } ) ;
419+ }
420+
421+ // Additional tsdk locations from settings
422+ const additionalLocations = config . get < string [ ] > ( "additionalTsdkLocations" , [ ] ) ;
423+ if ( additionalLocations . length > 0 ) {
424+ items . push ( {
425+ label : "" ,
426+ kind : vscode . QuickPickItemKind . Separator ,
427+ run : async ( ) => { } ,
428+ } ) ;
429+ for ( const loc of additionalLocations ) {
430+ const resolved = await resolveTsdkPathToExe ( loc ) ;
431+ if ( ! resolved ) continue ;
432+ if ( resolved . path === builtinExe . path ) continue ;
433+ if ( workspaceVersions . some ( ws => ws . exePath === resolved . path ) ) continue ;
434+ const isActive = currentExePath === resolved . path ;
435+ items . push ( {
436+ label : ( isActive ? "• " : "" ) + "Use Custom Version" ,
437+ description : resolved . version ,
438+ detail : resolved . path ,
439+ run : async ( ) => {
440+ await context . workspaceState . update ( useWorkspaceTsdkStorageKey , true ) ;
441+ await config . update ( "tsdk" , loc , vscode . ConfigurationTarget . Workspace ) ;
442+ outputChannel . appendLine ( `Switched to custom tsgo version at ${ loc } .` ) ;
443+ } ,
444+ } ) ;
445+ }
446+ }
447+
448+ const selected = await vscode . window . showQuickPick < VersionQuickPickItem > ( items , {
449+ placeHolder : "Select the TypeScript Native Preview version to use" ,
450+ } ) ;
451+
452+ if ( selected ) {
453+ await selected . run ( ) ;
454+ // Restart server to pick up the new version
455+ await vscode . commands . executeCommand ( "typescript.native-preview.restart" ) ;
456+ }
457+ }
458+
459+ /**
460+ * If the workspace has `@typescript/native-preview` installed and the user
461+ * hasn't already opted in or dismissed the prompt, ask whether they'd like
462+ * to use the workspace version.
463+ */
464+ export async function promptUseWorkspaceVersion ( context : vscode . ExtensionContext ) : Promise < void > {
465+ if ( ! vscode . workspace . isTrusted ) return ;
466+
467+ const useWorkspaceTsdk = context . workspaceState . get < boolean > ( useWorkspaceTsdkStorageKey , false ) ;
468+ if ( useWorkspaceTsdk ) return ; // already opted in
469+
470+ const suppressKey = "typescript.native-preview.suppressPromptWorkspaceTsdk" ;
471+ if ( context . workspaceState . get < boolean > ( suppressKey , false ) ) return ;
472+
473+ const workspaceVersions = await findWorkspaceNativePreviewPackages ( ) ;
474+ if ( workspaceVersions . length === 0 ) return ;
475+
476+ const wsVersion = workspaceVersions [ 0 ] ;
477+ const allow = "Allow" ;
478+ const dismiss = "Dismiss" ;
479+ const suppress = "Never in this Workspace" ;
480+
481+ const result = await vscode . window . showInformationMessage (
482+ `This workspace contains a TypeScript Native Preview version (${ wsVersion . version } ). Would you like to use the workspace version?` ,
483+ allow ,
484+ dismiss ,
485+ suppress ,
486+ ) ;
487+
488+ if ( result === allow ) {
489+ if ( ! vscode . workspace . isTrusted ) return ;
490+ await context . workspaceState . update ( useWorkspaceTsdkStorageKey , true ) ;
491+ const config = vscode . workspace . getConfiguration ( "typescript.native-preview" ) ;
492+ await config . update ( "tsdk" , wsVersion . tsdkPath , vscode . ConfigurationTarget . Workspace ) ;
493+ await vscode . commands . executeCommand ( "typescript.native-preview.restart" ) ;
494+ }
495+ else if ( result === suppress ) {
496+ await context . workspaceState . update ( suppressKey , true ) ;
285497 }
286498}
287499
0 commit comments