@@ -218,7 +218,7 @@ func (l *LanguageServer) Handle(ctx context.Context, _ *jsonrpc2.Conn, req *json
218218 case "textDocument/diagnostic" :
219219 return l .handleTextDocumentDiagnostic ()
220220 case "textDocument/didOpen" :
221- return handler .WithParams ( req , l .handleTextDocumentDidOpen )
221+ return handler .WithContextAndParams ( ctx , req , l .handleTextDocumentDidOpen )
222222 case "textDocument/didClose" :
223223 return handler .WithParams (req , l .handleTextDocumentDidClose )
224224 case "textDocument/didSave" :
@@ -875,19 +875,21 @@ func (l *LanguageServer) loadConfig(ctx context.Context, conf config.Config) {
875875 }
876876
877877 // Rego versions may have changed, so reload them.
878- allRegoVersions , err := config .AllRegoVersions (l .workspacePath (), & conf )
879- if err != nil {
880- l .log .Debug ("failed to reload rego versions: %s" , err )
881- } else {
882- l .loadedConfigAllRegoVersions .Clear ()
878+ if l .workspacePath () != "" {
879+ allRegoVersions , err := config .AllRegoVersions (l .workspacePath (), & conf )
880+ if err != nil {
881+ l .log .Debug ("failed to reload rego versions: %s" , err )
882+ } else {
883+ l .loadedConfigAllRegoVersions .Clear ()
883884
884- for k , v := range allRegoVersions {
885- l .loadedConfigAllRegoVersions .Set (k , v )
885+ for k , v := range allRegoVersions {
886+ l .loadedConfigAllRegoVersions .Set (k , v )
887+ }
886888 }
887889 }
888890
889891 // Enabled rules might have changed with the new config, so reload.
890- if err = l .loadEnabledRulesFromConfig (ctx , conf ); err != nil {
892+ if err : = l .loadEnabledRulesFromConfig (ctx , conf ); err != nil {
891893 l .log .Message ("failed to cache enabled rules: %s" , err )
892894 }
893895
@@ -1528,7 +1530,21 @@ func (l *LanguageServer) handleTextDocumentDefinition(params types.DefinitionPar
15281530 }, nil
15291531}
15301532
1531- func (l * LanguageServer ) handleTextDocumentDidOpen (params types.DidOpenTextDocumentParams ) (any , error ) {
1533+ func (l * LanguageServer ) handleTextDocumentDidOpen (
1534+ ctx context.Context ,
1535+ params types.DidOpenTextDocumentParams ,
1536+ ) (any , error ) {
1537+ // then we have started the server, and not yet received a suitable root to use.
1538+ if l .workspaceRootURI == "" {
1539+ err := l .updateRootURI (ctx ,
1540+ // get the URI of the file's immediate parent
1541+ l .fromPath (filepath .Dir (l .toPath (params .TextDocument .URI ))),
1542+ )
1543+ if err != nil {
1544+ l .log .Message ("failed to update server root URI: %w" , err )
1545+ }
1546+ }
1547+
15321548 // if the opened file is ignored, we only store the contents for file level operations like formatting
15331549 if l .ignoreURI (params .TextDocument .URI ) {
15341550 l .cache .SetIgnoredFileContents (params .TextDocument .URI , params .TextDocument .Text )
@@ -1905,40 +1921,6 @@ func (l *LanguageServer) handleInitialize(ctx context.Context, params types.Init
19051921 Capabilities : params .Capabilities ,
19061922 }
19071923
1908- // params.RootURI not expected to have a trailing slash, remove if present for consistency
1909- rootURI := strings .TrimSuffix (params .RootURI , string (os .PathSeparator ))
1910- if rootURI == "" {
1911- return nil , errors .New ("rootURI was not set by the client but is required" )
1912- }
1913-
1914- workspaceRootPath := uri .ToPath (l .client .Identifier , rootURI )
1915-
1916- configRoots , err := lsconfig .FindConfigRoots (workspaceRootPath )
1917- if err != nil {
1918- return nil , fmt .Errorf ("failed to find config roots: %w" , err )
1919- }
1920-
1921- l .workspaceRootURI = rootURI
1922-
1923- switch {
1924- case len (configRoots ) > 1 :
1925- l .log .Message ("warning: multiple configuration root directories found in workspace:" +
1926- "\n %s\n using %q as workspace root directory" ,
1927- strings .Join (configRoots , "\n " ), configRoots [0 ],
1928- )
1929-
1930- l .workspaceRootURI = uri .FromPath (l .client .Identifier , configRoots [0 ])
1931- case len (configRoots ) == 1 :
1932- l .log .Message ("using %q as workspace root directory" , configRoots [0 ])
1933-
1934- l .workspaceRootURI = uri .FromPath (l .client .Identifier , configRoots [0 ])
1935- default :
1936- l .log .Message (
1937- "using workspace root directory: %q, custom config not found — may be inherited from parent directory" ,
1938- workspaceRootPath ,
1939- )
1940- }
1941-
19421924 if l .client .Identifier == clients .IdentifierGeneric {
19431925 l .log .Message (
19441926 "unable to match client identifier for initializing client, using generic functionality: %s" ,
@@ -2029,45 +2011,94 @@ func (l *LanguageServer) handleInitialize(ctx context.Context, params types.Init
20292011 l .log .Message ("failed to cache enabled rules: %s" , err )
20302012 }
20312013
2032- if l .workspaceRootURI != "" {
2033- workspaceRootPath := l .workspacePath ()
2034-
2035- l .bundleCache = bundles .NewCache (workspaceRootPath , l .log )
2036-
2037- var configFilePath string
2038- if configFile , err := config .FindConfig (workspaceRootPath ); err == nil {
2039- configFilePath = configFile .Name ()
2040- } else if globalConfigDir := config .GlobalConfigDir (false ); globalConfigDir != "" {
2041- // the file might not exist and we only want to log we're using the global file if it does.
2042- if globalConfigFile := filepath .Join (globalConfigDir , "config.yaml" ); rio .IsFile (globalConfigFile ) {
2043- configFilePath = globalConfigFile
2044- }
2014+ if params .RootURI != "" {
2015+ err := l .updateRootURI (ctx , params .RootURI )
2016+ if err != nil {
2017+ l .log .Message ("failed to set rootURI: %w" , err )
20452018 }
2046-
2047- if configFilePath != "" {
2048- l .log .Message ("using config file: %s" , configFilePath )
2049- l .configWatcher .Watch (configFilePath )
2050- } else {
2051- l .log .Message ("no config file found for workspace" )
2019+ } else if params .WorkspaceFolders != nil && len (* params .WorkspaceFolders ) != 0 {
2020+ // note, using workspace folders is untested, and is based on the spec alone.
2021+ if len (* params .WorkspaceFolders ) > 1 {
2022+ l .log .Message ("cannot operate with more than one workspace folder, using: %s" , (* params .WorkspaceFolders )[0 ].URI )
20522023 }
20532024
2054- _ , failed , err := l .loadWorkspaceContents (ctx , false )
2055- for _ , f := range failed {
2056- l .log .Message ("failed to load file %s : %s " , f . URI , f . Error )
2025+ err := l .updateRootURI (ctx , ( * params . WorkspaceFolders )[ 0 ]. URI )
2026+ if err != nil {
2027+ l .log .Message ("failed to set rootURI to workspace folder : %w " , err )
20572028 }
2029+ }
20582030
2059- if err != nil {
2060- l .log .Message ("failed to load workspace contents: %s" , err )
2031+ return initializeResult , nil
2032+ }
2033+
2034+ func (l * LanguageServer ) updateRootURI (ctx context.Context , rootURI string ) error {
2035+ // rootURI not expected to have a trailing slash, remove if present for
2036+ // consistency
2037+ normalizedRootURI := strings .TrimSuffix (rootURI , string (os .PathSeparator ))
2038+
2039+ configRoots , err := lsconfig .FindConfigRoots (l .toPath (normalizedRootURI ))
2040+ if err != nil {
2041+ return fmt .Errorf ("failed to find config roots: %w" , err )
2042+ }
2043+
2044+ switch {
2045+ case len (configRoots ) > 1 :
2046+ l .log .Message ("warning: multiple configuration root directories found in workspace:" +
2047+ "\n %s\n using %q as workspace root directory" ,
2048+ strings .Join (configRoots , "\n " ), configRoots [0 ],
2049+ )
2050+
2051+ l .workspaceRootURI = uri .FromPath (l .client .Identifier , configRoots [0 ])
2052+ case len (configRoots ) == 1 :
2053+ l .log .Message ("using %q as workspace root directory" , configRoots [0 ])
2054+
2055+ l .workspaceRootURI = uri .FromPath (l .client .Identifier , configRoots [0 ])
2056+ default :
2057+ l .workspaceRootURI = rootURI
2058+
2059+ l .log .Message (
2060+ "using workspace root directory: %q, custom config not found — may be inherited from parent directory" ,
2061+ rootURI ,
2062+ )
2063+ }
2064+
2065+ workspaceRootPath := l .workspacePath ()
2066+
2067+ l .bundleCache = bundles .NewCache (workspaceRootPath , l .log )
2068+
2069+ var configFilePath string
2070+ if configFile , err := config .FindConfig (workspaceRootPath ); err == nil {
2071+ configFilePath = configFile .Name ()
2072+ } else if globalConfigDir := config .GlobalConfigDir (false ); globalConfigDir != "" {
2073+ // the file might not exist and we only want to log we're using the global file if it does.
2074+ if globalConfigFile := filepath .Join (globalConfigDir , "config.yaml" ); rio .IsFile (globalConfigFile ) {
2075+ configFilePath = globalConfigFile
20612076 }
2077+ }
20622078
2063- l .webServer .SetWorkspaceURI (l .workspaceRootURI )
2079+ if configFilePath != "" {
2080+ l .log .Message ("using config file: %s" , configFilePath )
2081+ l .configWatcher .Watch (configFilePath )
2082+ } else {
2083+ l .log .Message ("no config file found for workspace" )
2084+ }
20642085
2065- // 'OverwriteAggregates' is set to populate the cache's initial aggregate state.
2066- // Subsequent runs of lintWorkspaceJobs will not set this and use the cached state.
2067- l .lintWorkspaceJobs <- lintWorkspaceJob { Reason : "server initialize" , OverwriteAggregates : true }
2086+ _ , failed , err := l . loadWorkspaceContents ( ctx , false )
2087+ for _ , f := range failed {
2088+ l .log . Message ( "failed to load file %s: %s" , f . URI , f . Error )
20682089 }
20692090
2070- return initializeResult , nil
2091+ if err != nil {
2092+ l .log .Message ("failed to load workspace contents: %s" , err )
2093+ }
2094+
2095+ l .webServer .SetWorkspaceURI (l .workspaceRootURI )
2096+
2097+ // 'OverwriteAggregates' is set to populate the cache's initial aggregate state.
2098+ // Subsequent runs of lintWorkspaceJobs will not set this and use the cached state.
2099+ l .lintWorkspaceJobs <- lintWorkspaceJob {Reason : "server initialize" , OverwriteAggregates : true }
2100+
2101+ return nil
20712102}
20722103
20732104type fileLoadFailure struct {
0 commit comments