44 "context"
55 "errors"
66 "fmt"
7+ "slices"
78 "strings"
89
910 "github.com/jzelinskie/persistent"
@@ -12,6 +13,7 @@ import (
1213
1314 log "github.com/authzed/spicedb/internal/logging"
1415 "github.com/authzed/spicedb/pkg/development"
16+ "github.com/authzed/spicedb/pkg/genutil/slicez"
1517 developerv1 "github.com/authzed/spicedb/pkg/proto/developer/v1"
1618 "github.com/authzed/spicedb/pkg/schemadsl/compiler"
1719 "github.com/authzed/spicedb/pkg/schemadsl/generator"
@@ -34,11 +36,6 @@ func (s *Server) textDocDiagnostic(ctx context.Context, r *jsonrpc2.Request) (Fu
3436 return FullDocumentDiagnosticReport {}, err
3537 }
3638
37- log .Info ().
38- Str ("uri" , string (params .TextDocument .URI )).
39- Int ("diagnostics" , len (diagnostics )).
40- Msg ("diagnostics complete" )
41-
4239 return FullDocumentDiagnosticReport {
4340 Kind : "full" ,
4441 Items : diagnostics ,
@@ -57,42 +54,34 @@ func (s *Server) computeDiagnostics(ctx context.Context, uri lsp.DocumentURI) ([
5754 return & jsonrpc2.Error {Code : jsonrpc2 .CodeInternalError , Message : "file not found" }
5855 }
5956
57+ // We assume that the current uri that we are diagnosing is the root of a composable schema.
58+ overlayFS := newLSPOverlayFS (uriToSourceDir (uri ), files )
6059 devCtx , devErrs , err := development .NewDevContext (ctx , & developerv1.RequestContext {
6160 Schema : file .contents ,
6261 Relationships : nil ,
63- })
62+ }, development . WithSourceFS ( overlayFS ), development . WithRootFileName ( string ( uri )) )
6463 if err != nil {
6564 return err
6665 }
67-
6866 // Get errors.
69- for _ , devErr := range devErrs .GetInputErrors () {
70- diagnostics = append (diagnostics , lsp.Diagnostic {
71- Severity : lsp .Error ,
72- Range : lsp.Range {
73- Start : lsp.Position {Line : int (devErr .Line ) - 1 , Character : int (devErr .Column ) - 1 },
74- End : lsp.Position {Line : int (devErr .Line ) - 1 , Character : int (devErr .Column ) - 1 },
75- },
76- Message : devErr .Message ,
77- })
67+ // We filter out errors that are *not* specifically for URI.
68+ errors := devErrs .GetInputErrors ()
69+ errorsForURI := slicez .Filter (errors , func (developerError * developerv1.DeveloperError ) bool {
70+ return slices .Contains (developerError .Path , string (uri ))
71+ })
72+ for _ , devErr := range errorsForURI {
73+ diagnostics = append (diagnostics , newLspDiagnostic (devErr , lsp .Error ))
7874 }
7975
8076 // If there are no errors, we can also check for warnings.
81- if len (diagnostics ) == 0 {
77+ if len (errors ) == 0 {
8278 warnings , err := development .GetWarnings (ctx , devCtx )
8379 if err != nil {
8480 return err
8581 }
8682
8783 for _ , devWarning := range warnings {
88- diagnostics = append (diagnostics , lsp.Diagnostic {
89- Severity : lsp .Warning ,
90- Range : lsp.Range {
91- Start : lsp.Position {Line : int (devWarning .Line ) - 1 , Character : int (devWarning .Column ) - 1 },
92- End : lsp.Position {Line : int (devWarning .Line ) - 1 , Character : int (devWarning .Column ) - 1 },
93- },
94- Message : devWarning .Message ,
95- })
84+ diagnostics = append (diagnostics , newLspDiagnostic (devWarning , lsp .Warning ))
9685 }
9786 }
9887
@@ -101,10 +90,43 @@ func (s *Server) computeDiagnostics(ctx context.Context, uri lsp.DocumentURI) ([
10190 return nil , err
10291 }
10392
104- log .Info ().Int ("diagnostics" , len (diagnostics )).Str ("uri" , string (uri )).Msg ("computed diagnostics" )
10593 return diagnostics , nil
10694}
10795
96+ type DeveloperErrorWithPosition interface {
97+ // 1-indexed Line
98+ GetLine () uint32
99+ // 1-indexed Column
100+ GetColumn () uint32
101+ GetMessage () string
102+ }
103+
104+ func newLspDiagnostic (devErr DeveloperErrorWithPosition , severity lsp.DiagnosticSeverity ) lsp.Diagnostic {
105+ // lines and columns are 1-indexed
106+ return lsp.Diagnostic {
107+ Severity : severity ,
108+ Range : lsp.Range {
109+ Start : lsp.Position {Line : int (devErr .GetLine ()) - 1 , Character : int (devErr .GetColumn ()) - 1 },
110+ End : lsp.Position {Line : int (devErr .GetLine ()) - 1 , Character : int (devErr .GetColumn ()) - 1 },
111+ },
112+ Message : devErr .GetMessage (),
113+ }
114+ }
115+
116+ func newLspRange (resolved * development.SchemaReference ) * lsp.Range {
117+ // lines and columns are 0-indexed
118+ return & lsp.Range {
119+ Start : lsp.Position {
120+ Line : resolved .TargetPosition .LineNumber ,
121+ Character : resolved .TargetPosition .ColumnPosition + resolved .TargetNamePositionOffset ,
122+ },
123+ End : lsp.Position {
124+ Line : resolved .TargetPosition .LineNumber ,
125+ Character : resolved .TargetPosition .ColumnPosition + resolved .TargetNamePositionOffset + len (resolved .Text ),
126+ },
127+ }
128+ }
129+
108130func (s * Server ) textDocDidSave (ctx context.Context , r * jsonrpc2.Request , conn * jsonrpc2.Conn ) (any , error ) {
109131 params , err := unmarshalParams [lsp.DidSaveTextDocumentParams ](r )
110132 if err != nil {
@@ -171,15 +193,16 @@ func (s *Server) publishDiagnosticsIfNecessary(ctx context.Context, conn *jsonrp
171193 return nil
172194 }
173195
174- log .Debug ().
175- Str ("uri" , string (uri )).
176- Msg ("publishing diagnostics" )
177-
178196 diagnostics , err := s .computeDiagnostics (ctx , uri )
179197 if err != nil {
180198 return fmt .Errorf ("failed to compute diagnostics: %w" , err )
181199 }
182200
201+ log .Info ().
202+ Str ("uri" , string (uri )).
203+ Int ("diagnostics" , len (diagnostics )).
204+ Msg ("publishing diagnostics" )
205+
183206 return conn .Notify (ctx , "textDocument/publishDiagnostics" , lsp.PublishDiagnosticsParams {
184207 URI : uri ,
185208 Diagnostics : diagnostics ,
@@ -197,7 +220,8 @@ func (s *Server) getCompiledContents(path lsp.DocumentURI, files *persistent.Map
197220 return compiled , nil
198221 }
199222
200- justCompiled , derr , err := development .CompileSchema (file .contents )
223+ overlayFS := newLSPOverlayFS (uriToSourceDir (path ), files )
224+ justCompiled , derr , err := development .CompileSchema (file .contents , development .WithSourceFS (overlayFS ))
201225 if err != nil {
202226 return nil , err
203227 }
@@ -243,16 +267,7 @@ func (s *Server) textDocHover(_ context.Context, r *jsonrpc2.Request) (*Hover, e
243267
244268 var lspRange * lsp.Range
245269 if resolved .TargetPosition != nil {
246- lspRange = & lsp.Range {
247- Start : lsp.Position {
248- Line : resolved .TargetPosition .LineNumber ,
249- Character : resolved .TargetPosition .ColumnPosition + resolved .TargetNamePositionOffset ,
250- },
251- End : lsp.Position {
252- Line : resolved .TargetPosition .LineNumber ,
253- Character : resolved .TargetPosition .ColumnPosition + resolved .TargetNamePositionOffset + len (resolved .Text ),
254- },
255- }
270+ lspRange = newLspRange (resolved )
256271 }
257272
258273 if resolved .TargetSourceCode != "" {
@@ -282,6 +297,58 @@ func (s *Server) textDocHover(_ context.Context, r *jsonrpc2.Request) (*Hover, e
282297 return hoverContents , nil
283298}
284299
300+ func (s * Server ) textDocDefinition (_ context.Context , r * jsonrpc2.Request ) (* lsp.Location , error ) {
301+ params , err := unmarshalParams [lsp.TextDocumentPositionParams ](r )
302+ if err != nil {
303+ return nil , err
304+ }
305+
306+ var location * lsp.Location
307+ err = s .withFiles (func (files * persistent.Map [lsp.DocumentURI , trackedFile ]) error {
308+ compiled , err := s .getCompiledContents (params .TextDocument .URI , files )
309+ if err != nil {
310+ return err
311+ }
312+
313+ resolver , err := development .NewSchemaPositionMapper (compiled )
314+ if err != nil {
315+ return err
316+ }
317+
318+ position := input.Position {
319+ LineNumber : params .Position .Line ,
320+ ColumnPosition : params .Position .Character ,
321+ }
322+
323+ resolved , err := resolver .ReferenceAtPosition (input .Source ("schema" ), position )
324+ if err != nil {
325+ return err
326+ }
327+
328+ if resolved == nil || resolved .TargetPosition == nil {
329+ return nil
330+ }
331+
332+ // Determine the target file URI from TargetSource.
333+ targetURI := params .TextDocument .URI
334+ if resolved .TargetSource != nil && * resolved .TargetSource != "schema" {
335+ targetURI = resolveURI (params .TextDocument .URI , string (* resolved .TargetSource ))
336+ }
337+
338+ location = & lsp.Location {
339+ URI : targetURI ,
340+ Range : * newLspRange (resolved ),
341+ }
342+
343+ return nil
344+ })
345+ if err != nil {
346+ return nil , err
347+ }
348+
349+ return location , nil
350+ }
351+
285352func (s * Server ) textDocFormat (ctx context.Context , r * jsonrpc2.Request ) ([]lsp.TextEdit , error ) {
286353 params , err := unmarshalParams [lsp.DocumentFormattingParams ](r )
287354 if err != nil {
@@ -336,9 +403,6 @@ func (s *Server) initialize(_ context.Context, r *jsonrpc2.Request) (any, error)
336403 }
337404
338405 s .requestsDiagnostics = ip .Capabilities .SupportsPullDiagnostics ()
339- log .Debug ().
340- Bool ("requestsDiagnostics" , s .requestsDiagnostics ).
341- Msg ("initialize" )
342406
343407 if s .state != serverStateNotInitialized {
344408 return nil , invalidRequest (errors .New ("already initialized" ))
@@ -353,6 +417,7 @@ func (s *Server) initialize(_ context.Context, r *jsonrpc2.Request) (any, error)
353417 DocumentFormattingProvider : true ,
354418 DiagnosticProvider : & DiagnosticOptions {Identifier : "spicedb" , InterFileDependencies : false , WorkspaceDiagnostics : false },
355419 HoverProvider : true ,
420+ DefinitionProvider : true ,
356421 },
357422 }, nil
358423}
0 commit comments