internal/lsp: check for file URIs on LSP requests

In general, we expect all URIs to be file:// scheme. Silently ignore
requests that come in for other schemes. (In the command-line client we
panic since we should never see anything else.)

The calling convention for beginFileRequest is odd; see the function
comment.

Fixes golang/go#33699.

Change-Id: Ie721e9a85478f3a12975f6528cfbd28cc7910be8
Reviewed-on: https://go-review.googlesource.com/c/tools/+/219483
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
diff --git a/internal/lsp/cmd/cmd.go b/internal/lsp/cmd/cmd.go
index daf9091..7bb5cb9 100644
--- a/internal/lsp/cmd/cmd.go
+++ b/internal/lsp/cmd/cmd.go
@@ -293,6 +293,15 @@
 	}
 }
 
+// fileURI converts a DocumentURI to a file:// span.URI, panicking if it's not a file.
+func fileURI(uri protocol.DocumentURI) span.URI {
+	sURI := uri.SpanURI()
+	if !sURI.IsFile() {
+		panic(fmt.Sprintf("%q is not a file URI", uri))
+	}
+	return sURI
+}
+
 func (c *cmdClient) ShowMessage(ctx context.Context, p *protocol.ShowMessageParams) error { return nil }
 
 func (c *cmdClient) ShowMessageRequest(ctx context.Context, p *protocol.ShowMessageRequestParams) (*protocol.MessageActionItem, error) {
@@ -373,7 +382,7 @@
 	c.filesMu.Lock()
 	defer c.filesMu.Unlock()
 
-	file := c.getFile(ctx, p.URI.SpanURI())
+	file := c.getFile(ctx, fileURI(p.URI))
 	file.diagnostics = p.Diagnostics
 	return nil
 }
diff --git a/internal/lsp/cmd/definition.go b/internal/lsp/cmd/definition.go
index 6a9f2b5..2b2e3c0 100644
--- a/internal/lsp/cmd/definition.go
+++ b/internal/lsp/cmd/definition.go
@@ -109,7 +109,7 @@
 	if hover == nil {
 		return errors.Errorf("%v: not an identifier", from)
 	}
-	file = conn.AddFile(ctx, locs[0].URI.SpanURI())
+	file = conn.AddFile(ctx, fileURI(locs[0].URI))
 	if file.err != nil {
 		return errors.Errorf("%v: %v", from, file.err)
 	}
diff --git a/internal/lsp/cmd/implementation.go b/internal/lsp/cmd/implementation.go
index 91c4d59..e498372 100644
--- a/internal/lsp/cmd/implementation.go
+++ b/internal/lsp/cmd/implementation.go
@@ -72,7 +72,7 @@
 
 	var spans []string
 	for _, impl := range implementations {
-		f := conn.AddFile(ctx, impl.URI.SpanURI())
+		f := conn.AddFile(ctx, fileURI(impl.URI))
 		span, err := f.mapper.Span(impl)
 		if err != nil {
 			return err
diff --git a/internal/lsp/cmd/imports.go b/internal/lsp/cmd/imports.go
index 407509b..a6d00e9 100644
--- a/internal/lsp/cmd/imports.go
+++ b/internal/lsp/cmd/imports.go
@@ -74,7 +74,7 @@
 			continue
 		}
 		for _, c := range a.Edit.DocumentChanges {
-			if c.TextDocument.URI.SpanURI() == uri {
+			if fileURI(c.TextDocument.URI) == uri {
 				edits = append(edits, c.Edits...)
 			}
 		}
diff --git a/internal/lsp/cmd/references.go b/internal/lsp/cmd/references.go
index 573348d..5626019 100644
--- a/internal/lsp/cmd/references.go
+++ b/internal/lsp/cmd/references.go
@@ -73,7 +73,7 @@
 	}
 	var spans []string
 	for _, l := range locations {
-		f := conn.AddFile(ctx, l.URI.SpanURI())
+		f := conn.AddFile(ctx, fileURI(l.URI))
 		// convert location to span for user-friendly 1-indexed line
 		// and column numbers
 		span, err := f.mapper.Span(l)
diff --git a/internal/lsp/cmd/rename.go b/internal/lsp/cmd/rename.go
index 33121aa..57cf846 100644
--- a/internal/lsp/cmd/rename.go
+++ b/internal/lsp/cmd/rename.go
@@ -81,7 +81,7 @@
 	var orderedURIs []string
 	edits := map[span.URI][]protocol.TextEdit{}
 	for _, c := range edit.DocumentChanges {
-		uri := c.TextDocument.URI.SpanURI()
+		uri := fileURI(c.TextDocument.URI)
 		edits[uri] = append(edits[uri], c.Edits...)
 		orderedURIs = append(orderedURIs, string(uri))
 	}
diff --git a/internal/lsp/cmd/suggested_fix.go b/internal/lsp/cmd/suggested_fix.go
index b92cb25..5e8b1fa 100644
--- a/internal/lsp/cmd/suggested_fix.go
+++ b/internal/lsp/cmd/suggested_fix.go
@@ -87,7 +87,7 @@
 			continue
 		}
 		for _, c := range a.Edit.DocumentChanges {
-			if c.TextDocument.URI.SpanURI() == uri {
+			if fileURI(c.TextDocument.URI) == uri {
 				edits = append(edits, c.Edits...)
 			}
 		}
diff --git a/internal/lsp/code_action.go b/internal/lsp/code_action.go
index ea2274a..c9f85fd 100644
--- a/internal/lsp/code_action.go
+++ b/internal/lsp/code_action.go
@@ -20,19 +20,14 @@
 )
 
 func (s *Server) codeAction(ctx context.Context, params *protocol.CodeActionParams) ([]protocol.CodeAction, error) {
-	uri := params.TextDocument.URI.SpanURI()
-	view, err := s.session.ViewOf(uri)
-	if err != nil {
+	snapshot, fh, ok, err := s.beginFileRequest(params.TextDocument.URI, source.UnknownKind)
+	if !ok {
 		return nil, err
 	}
-	snapshot := view.Snapshot()
-	fh, err := snapshot.GetFile(uri)
-	if err != nil {
-		return nil, err
-	}
+	uri := fh.Identity().URI
 
 	// Determine the supported actions for this file kind.
-	supportedCodeActions, ok := view.Options().SupportedCodeActions[fh.Identity().Kind]
+	supportedCodeActions, ok := snapshot.View().Options().SupportedCodeActions[fh.Identity().Kind]
 	if !ok {
 		return nil, fmt.Errorf("no supported code actions for %v file kind", fh.Identity().Kind)
 	}
diff --git a/internal/lsp/command.go b/internal/lsp/command.go
index a5bccfc..a7907e6 100644
--- a/internal/lsp/command.go
+++ b/internal/lsp/command.go
@@ -5,7 +5,6 @@
 
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/lsp/source"
-	"golang.org/x/tools/internal/span"
 	errors "golang.org/x/xerrors"
 )
 
@@ -15,36 +14,27 @@
 		if len(params.Arguments) == 0 || len(params.Arguments) > 1 {
 			return nil, errors.Errorf("expected one file URI for call to `go mod tidy`, got %v", params.Arguments)
 		}
-		// Confirm that this action is being taken on a go.mod file.
-		uri := span.URIFromURI(params.Arguments[0].(string))
-		view, err := s.session.ViewOf(uri)
-		if err != nil {
+		uri := protocol.DocumentURI(params.Arguments[0].(string))
+		snapshot, _, ok, err := s.beginFileRequest(uri, source.Mod)
+		if !ok {
 			return nil, err
 		}
-		snapshot := view.Snapshot()
-		fh, err := snapshot.GetFile(uri)
-		if err != nil {
-			return nil, err
-		}
-		if fh.Identity().Kind != source.Mod {
-			return nil, errors.Errorf("%s is not a mod file", uri)
-		}
 		// Run go.mod tidy on the view.
-		if _, err := source.InvokeGo(ctx, view.Folder().Filename(), snapshot.Config(ctx).Env, "mod", "tidy"); err != nil {
+		if _, err := source.InvokeGo(ctx, snapshot.View().Folder().Filename(), snapshot.Config(ctx).Env, "mod", "tidy"); err != nil {
 			return nil, err
 		}
 	case "upgrade.dependency":
 		if len(params.Arguments) < 2 {
 			return nil, errors.Errorf("expected one file URI and one dependency for call to `go get`, got %v", params.Arguments)
 		}
-		uri := span.URIFromURI(params.Arguments[0].(string))
-		view, err := s.session.ViewOf(uri)
-		if err != nil {
+		uri := protocol.DocumentURI(params.Arguments[0].(string))
+		snapshot, _, ok, err := s.beginFileRequest(uri, source.UnknownKind)
+		if !ok {
 			return nil, err
 		}
 		dep := params.Arguments[1].(string)
 		// Run "go get" on the dependency to upgrade it to the latest version.
-		if _, err := source.InvokeGo(ctx, view.Folder().Filename(), view.Snapshot().Config(ctx).Env, "get", dep); err != nil {
+		if _, err := source.InvokeGo(ctx, snapshot.View().Folder().Filename(), snapshot.Config(ctx).Env, "get", dep); err != nil {
 			return nil, err
 		}
 	}
diff --git a/internal/lsp/completion.go b/internal/lsp/completion.go
index a430683..ea6af87 100644
--- a/internal/lsp/completion.go
+++ b/internal/lsp/completion.go
@@ -16,14 +16,8 @@
 )
 
 func (s *Server) completion(ctx context.Context, params *protocol.CompletionParams) (*protocol.CompletionList, error) {
-	uri := params.TextDocument.URI.SpanURI()
-	view, err := s.session.ViewOf(uri)
-	if err != nil {
-		return nil, err
-	}
-	snapshot := view.Snapshot()
-	fh, err := snapshot.GetFile(uri)
-	if err != nil {
+	snapshot, fh, ok, err := s.beginFileRequest(params.TextDocument.URI, source.UnknownKind)
+	if !ok {
 		return nil, err
 	}
 	var candidates []source.CompletionItem
@@ -51,7 +45,7 @@
 
 	// When using deep completions/fuzzy matching, report results as incomplete so
 	// client fetches updated completions after every key stroke.
-	options := view.Options()
+	options := snapshot.View().Options()
 	incompleteResults := options.DeepCompletion || options.Matcher == source.Fuzzy
 
 	items := toProtocolCompletionItems(candidates, rng, options)
diff --git a/internal/lsp/definition.go b/internal/lsp/definition.go
index 6008728..440a481 100644
--- a/internal/lsp/definition.go
+++ b/internal/lsp/definition.go
@@ -12,19 +12,10 @@
 )
 
 func (s *Server) definition(ctx context.Context, params *protocol.DefinitionParams) ([]protocol.Location, error) {
-	uri := params.TextDocument.URI.SpanURI()
-	view, err := s.session.ViewOf(uri)
-	if err != nil {
+	snapshot, fh, ok, err := s.beginFileRequest(params.TextDocument.URI, source.Go)
+	if !ok {
 		return nil, err
 	}
-	snapshot := view.Snapshot()
-	fh, err := snapshot.GetFile(uri)
-	if err != nil {
-		return nil, err
-	}
-	if fh.Identity().Kind != source.Go {
-		return nil, nil
-	}
 	ident, err := source.Identifier(ctx, snapshot, fh, params.Position)
 	if err != nil {
 		return nil, err
@@ -42,19 +33,10 @@
 }
 
 func (s *Server) typeDefinition(ctx context.Context, params *protocol.TypeDefinitionParams) ([]protocol.Location, error) {
-	uri := params.TextDocument.URI.SpanURI()
-	view, err := s.session.ViewOf(uri)
-	if err != nil {
+	snapshot, fh, ok, err := s.beginFileRequest(params.TextDocument.URI, source.Go)
+	if !ok {
 		return nil, err
 	}
-	snapshot := view.Snapshot()
-	fh, err := snapshot.GetFile(uri)
-	if err != nil {
-		return nil, err
-	}
-	if fh.Identity().Kind != source.Go {
-		return nil, nil
-	}
 	ident, err := source.Identifier(ctx, snapshot, fh, params.Position)
 	if err != nil {
 		return nil, err
diff --git a/internal/lsp/folding_range.go b/internal/lsp/folding_range.go
index 69c9f68..5bae8f5 100644
--- a/internal/lsp/folding_range.go
+++ b/internal/lsp/folding_range.go
@@ -8,24 +8,12 @@
 )
 
 func (s *Server) foldingRange(ctx context.Context, params *protocol.FoldingRangeParams) ([]protocol.FoldingRange, error) {
-	uri := params.TextDocument.URI.SpanURI()
-	view, err := s.session.ViewOf(uri)
-	if err != nil {
+	snapshot, fh, ok, err := s.beginFileRequest(params.TextDocument.URI, source.Go)
+	if !ok {
 		return nil, err
 	}
-	snapshot := view.Snapshot()
-	fh, err := snapshot.GetFile(uri)
-	if err != nil {
-		return nil, err
-	}
-	var ranges []*source.FoldingRangeInfo
-	switch fh.Identity().Kind {
-	case source.Go:
-		ranges, err = source.FoldingRange(ctx, snapshot, fh, view.Options().LineFoldingOnly)
-	case source.Mod:
-		ranges = nil
-	}
 
+	ranges, err := source.FoldingRange(ctx, snapshot, fh, snapshot.View().Options().LineFoldingOnly)
 	if err != nil {
 		return nil, err
 	}
diff --git a/internal/lsp/format.go b/internal/lsp/format.go
index 1f70ba7..33dd407 100644
--- a/internal/lsp/format.go
+++ b/internal/lsp/format.go
@@ -12,24 +12,11 @@
 )
 
 func (s *Server) formatting(ctx context.Context, params *protocol.DocumentFormattingParams) ([]protocol.TextEdit, error) {
-	uri := params.TextDocument.URI.SpanURI()
-	view, err := s.session.ViewOf(uri)
-	if err != nil {
+	snapshot, fh, ok, err := s.beginFileRequest(params.TextDocument.URI, source.Go)
+	if !ok {
 		return nil, err
 	}
-	snapshot := view.Snapshot()
-	fh, err := snapshot.GetFile(uri)
-	if err != nil {
-		return nil, err
-	}
-	var edits []protocol.TextEdit
-	switch fh.Identity().Kind {
-	case source.Go:
-		edits, err = source.Format(ctx, snapshot, fh)
-	case source.Mod:
-		return nil, nil
-	}
-
+	edits, err := source.Format(ctx, snapshot, fh)
 	if err != nil {
 		return nil, err
 	}
diff --git a/internal/lsp/general.go b/internal/lsp/general.go
index c476d56..d820576 100644
--- a/internal/lsp/general.go
+++ b/internal/lsp/general.go
@@ -234,6 +234,32 @@
 	return nil
 }
 
+// beginFileRequest checks preconditions for a file-oriented request and routes
+// it to a snapshot.
+// We don't want to return errors for benign conditions like wrong file type,
+// so callers should do if !ok { return err } rather than if err != nil.
+func (s *Server) beginFileRequest(pURI protocol.DocumentURI, expectKind source.FileKind) (source.Snapshot, source.FileHandle, bool, error) {
+	uri := pURI.SpanURI()
+	if !uri.IsFile() {
+		// Not a file URI. Stop processing the request, but don't return an error.
+		return nil, nil, false, nil
+	}
+	view, err := s.session.ViewOf(uri)
+	if err != nil {
+		return nil, nil, false, err
+	}
+	snapshot := view.Snapshot()
+	fh, err := snapshot.GetFile(uri)
+	if err != nil {
+		return nil, nil, false, err
+	}
+	if expectKind != source.UnknownKind && fh.Identity().Kind != expectKind {
+		// Wrong kind of file. Nothing to do.
+		return nil, nil, false, nil
+	}
+	return snapshot, fh, true, nil
+}
+
 func (s *Server) shutdown(ctx context.Context) error {
 	s.stateMu.Lock()
 	defer s.stateMu.Unlock()
diff --git a/internal/lsp/highlight.go b/internal/lsp/highlight.go
index c2f5071..7386ddc 100644
--- a/internal/lsp/highlight.go
+++ b/internal/lsp/highlight.go
@@ -14,26 +14,13 @@
 )
 
 func (s *Server) documentHighlight(ctx context.Context, params *protocol.DocumentHighlightParams) ([]protocol.DocumentHighlight, error) {
-	uri := params.TextDocument.URI.SpanURI()
-	view, err := s.session.ViewOf(uri)
-	if err != nil {
+	snapshot, fh, ok, err := s.beginFileRequest(params.TextDocument.URI, source.Go)
+	if !ok {
 		return nil, err
 	}
-	snapshot := view.Snapshot()
-	fh, err := snapshot.GetFile(uri)
+	rngs, err := source.Highlight(ctx, snapshot, fh, params.Position)
 	if err != nil {
-		return nil, err
-	}
-	var rngs []protocol.Range
-	switch fh.Identity().Kind {
-	case source.Go:
-		rngs, err = source.Highlight(ctx, snapshot, fh, params.Position)
-	case source.Mod:
-		return nil, nil
-	}
-
-	if err != nil {
-		log.Error(ctx, "no highlight", err, telemetry.URI.Of(uri))
+		log.Error(ctx, "no highlight", err, telemetry.URI.Of(params.TextDocument.URI))
 	}
 	return toProtocolHighlight(rngs), nil
 }
diff --git a/internal/lsp/hover.go b/internal/lsp/hover.go
index 4ec04a5..842c72f 100644
--- a/internal/lsp/hover.go
+++ b/internal/lsp/hover.go
@@ -12,19 +12,10 @@
 )
 
 func (s *Server) hover(ctx context.Context, params *protocol.HoverParams) (*protocol.Hover, error) {
-	uri := params.TextDocument.URI.SpanURI()
-	view, err := s.session.ViewOf(uri)
-	if err != nil {
+	snapshot, fh, ok, err := s.beginFileRequest(params.TextDocument.URI, source.Go)
+	if !ok {
 		return nil, err
 	}
-	snapshot := view.Snapshot()
-	fh, err := snapshot.GetFile(uri)
-	if err != nil {
-		return nil, err
-	}
-	if fh.Identity().Kind != source.Go {
-		return nil, nil
-	}
 	ident, err := source.Identifier(ctx, snapshot, fh, params.Position)
 	if err != nil {
 		return nil, nil
@@ -37,13 +28,13 @@
 	if err != nil {
 		return nil, err
 	}
-	hover, err := source.FormatHover(h, view.Options())
+	hover, err := source.FormatHover(h, snapshot.View().Options())
 	if err != nil {
 		return nil, err
 	}
 	return &protocol.Hover{
 		Contents: protocol.MarkupContent{
-			Kind:  view.Options().PreferredContentFormat,
+			Kind:  snapshot.View().Options().PreferredContentFormat,
 			Value: hover,
 		},
 		Range: rng,
diff --git a/internal/lsp/implementation.go b/internal/lsp/implementation.go
index 4fb0219..e4b3650 100644
--- a/internal/lsp/implementation.go
+++ b/internal/lsp/implementation.go
@@ -12,18 +12,9 @@
 )
 
 func (s *Server) implementation(ctx context.Context, params *protocol.ImplementationParams) ([]protocol.Location, error) {
-	uri := params.TextDocument.URI.SpanURI()
-	view, err := s.session.ViewOf(uri)
-	if err != nil {
+	snapshot, fh, ok, err := s.beginFileRequest(params.TextDocument.URI, source.Go)
+	if !ok {
 		return nil, err
 	}
-	snapshot := view.Snapshot()
-	fh, err := snapshot.GetFile(uri)
-	if err != nil {
-		return nil, err
-	}
-	if fh.Identity().Kind != source.Go {
-		return nil, nil
-	}
 	return source.Implementation(ctx, snapshot, fh, params.Position)
 }
diff --git a/internal/lsp/link.go b/internal/lsp/link.go
index c457d7e..fcde27b 100644
--- a/internal/lsp/link.go
+++ b/internal/lsp/link.go
@@ -21,19 +21,12 @@
 )
 
 func (s *Server) documentLink(ctx context.Context, params *protocol.DocumentLinkParams) ([]protocol.DocumentLink, error) {
-	uri := params.TextDocument.URI.SpanURI()
-	view, err := s.session.ViewOf(uri)
-	if err != nil {
-		return nil, err
-	}
-	fh, err := view.Snapshot().GetFile(uri)
-	if err != nil {
-		return nil, err
-	}
 	// TODO(golang/go#36501): Support document links for go.mod files.
-	if fh.Identity().Kind == source.Mod {
-		return nil, nil
+	snapshot, fh, ok, err := s.beginFileRequest(params.TextDocument.URI, source.Go)
+	if !ok {
+		return nil, err
 	}
+	view := snapshot.View()
 	file, _, m, _, err := view.Session().Cache().ParseGoHandle(fh, source.ParseFull).Parse(ctx)
 	if err != nil {
 		return nil, err
diff --git a/internal/lsp/references.go b/internal/lsp/references.go
index 068cfa6..57e92c0 100644
--- a/internal/lsp/references.go
+++ b/internal/lsp/references.go
@@ -12,22 +12,11 @@
 )
 
 func (s *Server) references(ctx context.Context, params *protocol.ReferenceParams) ([]protocol.Location, error) {
-	uri := params.TextDocument.URI.SpanURI()
-	view, err := s.session.ViewOf(uri)
-	if err != nil {
+	snapshot, fh, ok, err := s.beginFileRequest(params.TextDocument.URI, source.Go)
+	if !ok {
 		return nil, err
 	}
-	snapshot := view.Snapshot()
-	fh, err := snapshot.GetFile(uri)
-	if err != nil {
-		return nil, err
-	}
-	// Find all references to the identifier at the position.
-	if fh.Identity().Kind != source.Go {
-		return nil, nil
-	}
-
-	references, err := source.References(ctx, view.Snapshot(), fh, params.Position, params.Context.IncludeDeclaration)
+	references, err := source.References(ctx, snapshot, fh, params.Position, params.Context.IncludeDeclaration)
 	if err != nil {
 		return nil, err
 	}
diff --git a/internal/lsp/rename.go b/internal/lsp/rename.go
index 4043b14..9fe59e9 100644
--- a/internal/lsp/rename.go
+++ b/internal/lsp/rename.go
@@ -12,20 +12,10 @@
 )
 
 func (s *Server) rename(ctx context.Context, params *protocol.RenameParams) (*protocol.WorkspaceEdit, error) {
-	uri := params.TextDocument.URI.SpanURI()
-	view, err := s.session.ViewOf(uri)
-	if err != nil {
+	snapshot, fh, ok, err := s.beginFileRequest(params.TextDocument.URI, source.Go)
+	if !ok {
 		return nil, err
 	}
-	snapshot := view.Snapshot()
-	fh, err := snapshot.GetFile(uri)
-	if err != nil {
-		return nil, err
-	}
-	if fh.Identity().Kind != source.Go {
-		return nil, nil
-	}
-
 	edits, err := source.Rename(ctx, snapshot, fh, params.Position, params.NewName)
 	if err != nil {
 		return nil, err
@@ -45,20 +35,10 @@
 }
 
 func (s *Server) prepareRename(ctx context.Context, params *protocol.PrepareRenameParams) (*protocol.Range, error) {
-	uri := params.TextDocument.URI.SpanURI()
-	view, err := s.session.ViewOf(uri)
-	if err != nil {
+	snapshot, fh, ok, err := s.beginFileRequest(params.TextDocument.URI, source.Go)
+	if !ok {
 		return nil, err
 	}
-	snapshot := view.Snapshot()
-	fh, err := snapshot.GetFile(uri)
-	if err != nil {
-		return nil, err
-	}
-	if fh.Identity().Kind != source.Go {
-		return nil, nil
-	}
-
 	// Do not return errors here, as it adds clutter.
 	// Returning a nil result means there is not a valid rename.
 	item, err := source.PrepareRename(ctx, snapshot, fh, params.Position)
diff --git a/internal/lsp/server.go b/internal/lsp/server.go
index f4ba6b6..dca4d65 100644
--- a/internal/lsp/server.go
+++ b/internal/lsp/server.go
@@ -95,17 +95,17 @@
 	paramMap := params.(map[string]interface{})
 	if method == "gopls/diagnoseFiles" {
 		for _, file := range paramMap["files"].([]interface{}) {
-			uri := span.URIFromURI(file.(string))
-			view, err := s.session.ViewOf(uri)
-			if err != nil {
+			snapshot, fh, ok, err := s.beginFileRequest(protocol.DocumentURI(file.(string)), source.UnknownKind)
+			if !ok {
 				return nil, err
 			}
-			fileID, diagnostics, err := source.FileDiagnostics(ctx, view.Snapshot(), uri)
+
+			fileID, diagnostics, err := source.FileDiagnostics(ctx, snapshot, fh.Identity().URI)
 			if err != nil {
 				return nil, err
 			}
 			if err := s.client.PublishDiagnostics(ctx, &protocol.PublishDiagnosticsParams{
-				URI:         protocol.URIFromSpanURI(uri),
+				URI:         protocol.URIFromSpanURI(fh.Identity().URI),
 				Diagnostics: toProtocolDiagnostics(diagnostics),
 				Version:     fileID.Version,
 			}); err != nil {
diff --git a/internal/lsp/signature_help.go b/internal/lsp/signature_help.go
index c514d58..a978fd8 100644
--- a/internal/lsp/signature_help.go
+++ b/internal/lsp/signature_help.go
@@ -14,19 +14,10 @@
 )
 
 func (s *Server) signatureHelp(ctx context.Context, params *protocol.SignatureHelpParams) (*protocol.SignatureHelp, error) {
-	uri := params.TextDocument.URI.SpanURI()
-	view, err := s.session.ViewOf(uri)
-	if err != nil {
+	snapshot, fh, ok, err := s.beginFileRequest(params.TextDocument.URI, source.Go)
+	if !ok {
 		return nil, err
 	}
-	snapshot := view.Snapshot()
-	fh, err := snapshot.GetFile(uri)
-	if err != nil {
-		return nil, err
-	}
-	if fh.Identity().Kind != source.Go {
-		return nil, nil
-	}
 	info, activeParameter, err := source.SignatureHelp(ctx, snapshot, fh, params.Position)
 	if err != nil {
 		log.Print(ctx, "no signature help", tag.Of("At", params.Position), tag.Of("Failure", err))
diff --git a/internal/lsp/symbols.go b/internal/lsp/symbols.go
index b0d9371..b9f0b75 100644
--- a/internal/lsp/symbols.go
+++ b/internal/lsp/symbols.go
@@ -18,26 +18,13 @@
 	ctx, done := trace.StartSpan(ctx, "lsp.Server.documentSymbol")
 	defer done()
 
-	uri := params.TextDocument.URI.SpanURI()
-	view, err := s.session.ViewOf(uri)
-	if err != nil {
-		return nil, err
+	snapshot, fh, ok, err := s.beginFileRequest(params.TextDocument.URI, source.Go)
+	if !ok {
+		return []protocol.DocumentSymbol{}, err
 	}
-	snapshot := view.Snapshot()
-	fh, err := snapshot.GetFile(uri)
+	symbols, err := source.DocumentSymbols(ctx, snapshot, fh)
 	if err != nil {
-		return nil, err
-	}
-	var symbols []protocol.DocumentSymbol
-	switch fh.Identity().Kind {
-	case source.Go:
-		symbols, err = source.DocumentSymbols(ctx, snapshot, fh)
-	case source.Mod:
-		return []protocol.DocumentSymbol{}, nil
-	}
-
-	if err != nil {
-		log.Error(ctx, "DocumentSymbols failed", err, telemetry.URI.Of(uri))
+		log.Error(ctx, "DocumentSymbols failed", err, telemetry.URI.Of(fh.Identity().URI))
 		return []protocol.DocumentSymbol{}, nil
 	}
 	return symbols, nil
diff --git a/internal/lsp/text_synchronization.go b/internal/lsp/text_synchronization.go
index e4db204..cb05419 100644
--- a/internal/lsp/text_synchronization.go
+++ b/internal/lsp/text_synchronization.go
@@ -17,9 +17,14 @@
 )
 
 func (s *Server) didOpen(ctx context.Context, params *protocol.DidOpenTextDocumentParams) error {
+	uri := params.TextDocument.URI.SpanURI()
+	if !uri.IsFile() {
+		return nil
+	}
+
 	_, err := s.didModifyFiles(ctx, []source.FileModification{
 		{
-			URI:        params.TextDocument.URI.SpanURI(),
+			URI:        uri,
 			Action:     source.Open,
 			Version:    params.TextDocument.Version,
 			Text:       []byte(params.TextDocument.Text),
@@ -31,6 +36,10 @@
 
 func (s *Server) didChange(ctx context.Context, params *protocol.DidChangeTextDocumentParams) error {
 	uri := params.TextDocument.URI.SpanURI()
+	if !uri.IsFile() {
+		return nil
+	}
+
 	text, err := s.changedText(ctx, uri, params.ContentChanges)
 	if err != nil {
 		return err
@@ -65,8 +74,12 @@
 func (s *Server) didChangeWatchedFiles(ctx context.Context, params *protocol.DidChangeWatchedFilesParams) error {
 	var modifications []source.FileModification
 	for _, change := range params.Changes {
+		uri := change.URI.SpanURI()
+		if !uri.IsFile() {
+			continue
+		}
 		modifications = append(modifications, source.FileModification{
-			URI:    change.URI.SpanURI(),
+			URI:    uri,
 			Action: changeTypeToFileAction(change.Type),
 			OnDisk: true,
 		})
@@ -76,8 +89,13 @@
 }
 
 func (s *Server) didSave(ctx context.Context, params *protocol.DidSaveTextDocumentParams) error {
+	uri := params.TextDocument.URI.SpanURI()
+	if !uri.IsFile() {
+		return nil
+	}
+
 	c := source.FileModification{
-		URI:     params.TextDocument.URI.SpanURI(),
+		URI:     uri,
 		Action:  source.Save,
 		Version: params.TextDocument.Version,
 	}
@@ -89,9 +107,14 @@
 }
 
 func (s *Server) didClose(ctx context.Context, params *protocol.DidCloseTextDocumentParams) error {
+	uri := params.TextDocument.URI.SpanURI()
+	if !uri.IsFile() {
+		return nil
+	}
+
 	_, err := s.didModifyFiles(ctx, []source.FileModification{
 		{
-			URI:     params.TextDocument.URI.SpanURI(),
+			URI:     uri,
 			Action:  source.Close,
 			Version: -1,
 			Text:    nil,
diff --git a/internal/span/uri.go b/internal/span/uri.go
index a794a2d..f8487b9 100644
--- a/internal/span/uri.go
+++ b/internal/span/uri.go
@@ -20,6 +20,10 @@
 // URI represents the full URI for a file.
 type URI string
 
+func (uri URI) IsFile() bool {
+	return strings.HasPrefix(string(uri), "file://")
+}
+
 // Filename returns the file path for the given URI.
 // It is an error to call this on a URI that is not a valid filename.
 func (uri URI) Filename() string {