@@ -703,31 +703,10 @@ export const Zls: Info = {
703703export const CSharp : Info = {
704704 id : "csharp" ,
705705 root : NearestRoot ( [ ".slnx" , ".sln" , ".csproj" , "global.json" ] ) ,
706- extensions : [ ".cs" ] ,
706+ extensions : [ ".cs" , ".csx" ] ,
707707 async spawn ( root ) {
708- let bin = which ( "roslyn-language-server" )
709- if ( ! bin ) {
710- if ( ! which ( "dotnet" ) ) {
711- log . error ( ".NET SDK is required to install roslyn-language-server" )
712- return
713- }
714-
715- if ( Flag . OPENCODE_DISABLE_LSP_DOWNLOAD ) return
716- log . info ( "installing roslyn-language-server via dotnet tool" )
717- const proc = Process . spawn ( [ "dotnet" , "tool" , "install" , "--global" , "roslyn-language-server" , "--prerelease" ] , {
718- stdout : "pipe" ,
719- stderr : "pipe" ,
720- stdin : "pipe" ,
721- } )
722- const exit = await proc . exited
723- if ( exit !== 0 ) {
724- log . error ( "Failed to install roslyn-language-server" )
725- return
726- }
727-
728- bin = path . join ( Global . Path . bin , "roslyn-language-server" + ( process . platform === "win32" ? ".exe" : "" ) )
729- log . info ( `installed roslyn-language-server` , { bin } )
730- }
708+ const bin = await getRoslynLanguageServer ( )
709+ if ( ! bin ) return
731710
732711 return {
733712 process : spawn ( bin , [ "--stdio" , "--autoLoadProjects" ] , {
@@ -737,6 +716,135 @@ export const CSharp: Info = {
737716 } ,
738717}
739718
719+ export const Razor : Info = {
720+ id : "razor" ,
721+ root : NearestRoot ( [ ".slnx" , ".sln" , ".csproj" , "global.json" ] ) ,
722+ extensions : [ ".razor" , ".cshtml" ] ,
723+ async spawn ( root ) {
724+ const bin = await getRoslynLanguageServer ( )
725+ if ( ! bin ) return
726+
727+ const razor = await findVscodeRazorExtension ( )
728+ if ( ! razor ) {
729+ log . info ( "VS Code C# extension with Razor support not found, skipping Razor LSP" )
730+ return
731+ }
732+
733+ log . info ( "using VS Code Razor extension for roslyn-language-server" , { extension : razor . extension } )
734+ return {
735+ process : spawn (
736+ bin ,
737+ [
738+ "--stdio" ,
739+ "--autoLoadProjects" ,
740+ `--razorSourceGenerator=${ razor . compiler } ` ,
741+ `--razorDesignTimePath=${ razor . targets } ` ,
742+ "--extension" ,
743+ razor . extension ,
744+ ] ,
745+ {
746+ cwd : root ,
747+ } ,
748+ ) ,
749+ }
750+ } ,
751+ }
752+
753+ let roslynLanguageServerInstall : Promise < string | undefined > | undefined
754+
755+ async function getRoslynLanguageServer ( ) {
756+ const existing = which ( "roslyn-language-server" )
757+ if ( existing ) return existing
758+
759+ const global = await roslynLanguageServerGlobalPath ( )
760+ if ( global ) return global
761+
762+ roslynLanguageServerInstall ||= installRoslynLanguageServer ( ) . finally ( ( ) => {
763+ roslynLanguageServerInstall = undefined
764+ } )
765+ return roslynLanguageServerInstall
766+ }
767+
768+ async function installRoslynLanguageServer ( ) {
769+ if ( ! which ( "dotnet" ) ) {
770+ log . error ( ".NET SDK is required to install roslyn-language-server" )
771+ return
772+ }
773+
774+ if ( Flag . OPENCODE_DISABLE_LSP_DOWNLOAD ) return
775+ log . info ( "installing roslyn-language-server via dotnet tool" )
776+ const proc = Process . spawn ( [ "dotnet" , "tool" , "install" , "--global" , "roslyn-language-server" , "--prerelease" ] , {
777+ stdout : "pipe" ,
778+ stderr : "pipe" ,
779+ stdin : "pipe" ,
780+ } )
781+ const exit = await proc . exited
782+ if ( exit !== 0 ) {
783+ log . error ( "Failed to install roslyn-language-server" )
784+ return
785+ }
786+
787+ const resolved = which ( "roslyn-language-server" )
788+ if ( resolved ) {
789+ log . info ( `installed roslyn-language-server` , { bin : resolved } )
790+ return resolved
791+ }
792+
793+ const global = await roslynLanguageServerGlobalPath ( )
794+ if ( global ) {
795+ log . info ( `installed roslyn-language-server` , { bin : global } )
796+ return global
797+ }
798+
799+ log . error ( "Installed roslyn-language-server but could not resolve executable" )
800+ }
801+
802+ async function roslynLanguageServerGlobalPath ( ) {
803+ const bin = path . join (
804+ process . env . DOTNET_CLI_HOME ?? os . homedir ( ) ,
805+ ".dotnet" ,
806+ "tools" ,
807+ "roslyn-language-server" + ( process . platform === "win32" ? ".cmd" : "" ) ,
808+ )
809+ return ( await pathExists ( bin ) ) ? bin : undefined
810+ }
811+
812+ async function findVscodeRazorExtension ( ) {
813+ const roots = [
814+ process . env . VSCODE_EXTENSIONS ,
815+ path . join ( os . homedir ( ) , ".vscode" , "extensions" ) ,
816+ path . join ( os . homedir ( ) , ".vscode-insiders" , "extensions" ) ,
817+ path . join ( os . homedir ( ) , ".vscode-server" , "extensions" ) ,
818+ path . join ( os . homedir ( ) , ".vscode-server-insiders" , "extensions" ) ,
819+ ] . filter ( ( item ) => item !== undefined )
820+
821+ for ( const root of [ ...new Set ( roots ) ] ) {
822+ const entries = await fs . readdir ( root , { withFileTypes : true } ) . catch ( ( ) => [ ] )
823+ const candidates = await Promise . all (
824+ entries
825+ . filter ( ( entry ) => entry . isDirectory ( ) && entry . name . startsWith ( "ms-dotnettools.csharp-" ) )
826+ . map ( async ( entry ) => ( {
827+ path : path . join ( root , entry . name , ".razorExtension" ) ,
828+ modified : ( await fs . stat ( path . join ( root , entry . name ) ) . catch ( ( ) => undefined ) ) ?. mtimeMs ?? 0 ,
829+ } ) ) ,
830+ )
831+ for ( const entry of candidates . sort ( ( a , b ) => b . modified - a . modified ) . map ( ( candidate ) => candidate . path ) ) {
832+ const result = {
833+ compiler : path . join ( entry , "Microsoft.CodeAnalysis.Razor.Compiler.dll" ) ,
834+ targets : path . join ( entry , "Targets" , "Microsoft.NET.Sdk.Razor.DesignTime.targets" ) ,
835+ extension : path . join ( entry , "Microsoft.VisualStudioCode.RazorExtension.dll" ) ,
836+ }
837+ if (
838+ ( await pathExists ( result . compiler ) ) &&
839+ ( await pathExists ( result . targets ) ) &&
840+ ( await pathExists ( result . extension ) )
841+ ) {
842+ return result
843+ }
844+ }
845+ }
846+ }
847+
740848export const FSharp : Info = {
741849 id : "fsharp" ,
742850 root : NearestRoot ( [ ".slnx" , ".sln" , ".fsproj" , "global.json" ] ) ,
0 commit comments