1
0
mirror of https://github.com/golang/go synced 2024-11-18 11:44:45 -07:00

internal/lsp: extract view to its own package

This allows us to write the lsp verbs in terms of a stable underlying source
management layer.
This should make it easier to refactor the underlying layer to add more powerful
caching and incremental modes as we go.

Change-Id: Iab97b061d80394a6fa6748a93a4c68f2deb46129
Reviewed-on: https://go-review.googlesource.com/c/147201
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
Ian Cottrell 2018-11-02 16:15:31 -04:00
parent f1b4bd93c9
commit 9b5bafe36f
6 changed files with 82 additions and 47 deletions

View File

@ -11,10 +11,11 @@ import (
"golang.org/x/tools/go/packages"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
)
func (v *view) diagnostics(uri protocol.DocumentURI) (map[string][]protocol.Diagnostic, error) {
pkg, err := v.typeCheck(uri)
func diagnostics(v *source.View, uri protocol.DocumentURI) (map[string][]protocol.Diagnostic, error) {
pkg, err := v.TypeCheck(uri)
if err != nil {
return nil, err
}

View File

@ -15,6 +15,7 @@ import (
"golang.org/x/tools/go/packages"
"golang.org/x/tools/go/packages/packagestest"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
)
func TestDiagnostics(t *testing.T) {
@ -84,12 +85,12 @@ func testDiagnostics(t *testing.T, exporter packagestest.Exporter) {
if err != nil {
t.Fatal(err)
}
v := newView()
v.config = exported.Config
v.config.Mode = packages.LoadSyntax
v := source.NewView()
v.Config = exported.Config
v.Config.Mode = packages.LoadSyntax
for _, pkg := range pkgs {
for _, filename := range pkg.GoFiles {
diagnostics, err := v.diagnostics(filenameToURI(filename))
diagnostics, err := diagnostics(v, source.ToURI(filename))
if err != nil {
t.Fatal(err)
}

View File

@ -10,11 +10,12 @@ import (
"go/format"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
)
// format formats a document with a given range.
func (s *server) format(uri protocol.DocumentURI, rng *protocol.Range) ([]protocol.TextEdit, error) {
data, err := s.readActiveFile(uri)
// formatRange formats a document with a given range.
func formatRange(v *source.View, uri protocol.DocumentURI, rng *protocol.Range) ([]protocol.TextEdit, error) {
data, err := v.ReadActiveFile(uri)
if err != nil {
return nil, err
}

View File

@ -11,6 +11,7 @@ import (
"golang.org/x/tools/internal/jsonrpc2"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
)
// RunServer starts an LSP server on the supplied stream, and waits until the
@ -28,7 +29,7 @@ type server struct {
initializedMu sync.Mutex
initialized bool // set once the server has received "initialize" request
*view
view *source.View
}
func (s *server) Initialize(ctx context.Context, params *protocol.InitializeParams) (*protocol.InitializeResult, error) {
@ -37,7 +38,7 @@ func (s *server) Initialize(ctx context.Context, params *protocol.InitializePara
if s.initialized {
return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidRequest, "server already initialized")
}
s.view = newView()
s.view = source.NewView()
s.initialized = true
return &protocol.InitializeResult{
Capabilities: protocol.ServerCapabilities{
@ -110,15 +111,13 @@ func (s *server) DidChange(ctx context.Context, params *protocol.DidChangeTextDo
}
func (s *server) cacheAndDiagnoseFile(ctx context.Context, uri protocol.DocumentURI, text string) {
s.view.activeFilesMu.Lock()
s.view.activeFiles[uri] = []byte(text)
s.view.activeFilesMu.Unlock()
s.view.SetActiveFileContent(uri, []byte(text))
go func() {
reports, err := s.diagnostics(uri)
reports, err := diagnostics(s.view, uri)
if err == nil {
for filename, diagnostics := range reports {
s.client.PublishDiagnostics(ctx, &protocol.PublishDiagnosticsParams{
URI: filenameToURI(filename),
URI: source.ToURI(filename),
Diagnostics: diagnostics,
})
}
@ -140,7 +139,7 @@ func (s *server) DidSave(context.Context, *protocol.DidSaveTextDocumentParams) e
}
func (s *server) DidClose(ctx context.Context, params *protocol.DidCloseTextDocumentParams) error {
s.clearActiveFile(params.TextDocument.URI)
s.view.ClearActiveFile(params.TextDocument.URI)
return nil
}
@ -213,11 +212,11 @@ func (s *server) ColorPresentation(context.Context, *protocol.ColorPresentationP
}
func (s *server) Formatting(ctx context.Context, params *protocol.DocumentFormattingParams) ([]protocol.TextEdit, error) {
return s.format(params.TextDocument.URI, nil)
return formatRange(s.view, params.TextDocument.URI, nil)
}
func (s *server) RangeFormatting(ctx context.Context, params *protocol.DocumentRangeFormattingParams) ([]protocol.TextEdit, error) {
return s.format(params.TextDocument.URI, &params.Range)
return formatRange(s.view, params.TextDocument.URI, &params.Range)
}
func (s *server) OnTypeFormatting(context.Context, *protocol.DocumentOnTypeFormattingParams) ([]protocol.TextEdit, error) {

View File

@ -0,0 +1,32 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package source
import (
"fmt"
"path/filepath"
"strings"
"golang.org/x/tools/internal/lsp/protocol"
)
const fileSchemePrefix = "file://"
// FromURI gets the file path for a given URI.
// It will return an error if the uri is not valid, or if the URI was not
// a file URI
func FromURI(uri protocol.DocumentURI) (string, error) {
s := string(uri)
if !strings.HasPrefix(s, fileSchemePrefix) {
return "", fmt.Errorf("only file URI's are supported, got %v", uri)
}
return filepath.FromSlash(s[len(fileSchemePrefix):]), nil
}
// ToURI returns a protocol URI for the supplied path.
// It will always have the file scheme.
func ToURI(path string) protocol.DocumentURI {
return protocol.DocumentURI(fileSchemePrefix + filepath.ToSlash(path))
}

View File

@ -2,30 +2,30 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package lsp
package source
import (
"fmt"
"go/token"
"strings"
"sync"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/internal/lsp/protocol"
)
type view struct {
type View struct {
Config *packages.Config
activeFilesMu sync.Mutex
activeFiles map[protocol.DocumentURI][]byte
config *packages.Config
fset *token.FileSet
}
func newView() *view {
func NewView() *View {
fset := token.NewFileSet()
return &view{
config: &packages.Config{
return &View{
Config: &packages.Config{
Mode: packages.LoadSyntax,
Fset: fset,
Tests: true,
@ -35,53 +35,54 @@ func newView() *view {
}
}
func (v *view) overlay() map[string][]byte {
func (v *View) overlay() map[string][]byte {
over := make(map[string][]byte)
v.activeFilesMu.Lock()
defer v.activeFilesMu.Unlock()
for uri, content := range v.activeFiles {
over[uriToFilename(uri)] = content
filename, err := FromURI(uri)
if err == nil {
over[filename] = content
}
}
return over
}
func (v *view) readActiveFile(uri protocol.DocumentURI) ([]byte, error) {
func (v *View) SetActiveFileContent(uri protocol.DocumentURI, content []byte) {
v.activeFilesMu.Lock()
defer v.activeFilesMu.Unlock()
v.activeFiles[uri] = content
v.activeFilesMu.Unlock()
}
func (v *View) ReadActiveFile(uri protocol.DocumentURI) ([]byte, error) {
v.activeFilesMu.Lock()
content, ok := v.activeFiles[uri]
v.activeFilesMu.Unlock()
if !ok {
return nil, fmt.Errorf("file not found: %s", uri)
return nil, fmt.Errorf("uri not found: %s", uri)
}
return content, nil
}
func (v *view) clearActiveFile(uri protocol.DocumentURI) {
func (v *View) ClearActiveFile(uri protocol.DocumentURI) {
v.activeFilesMu.Lock()
delete(v.activeFiles, uri)
v.activeFilesMu.Unlock()
}
// typeCheck type-checks the package for the given package path.
func (v *view) typeCheck(uri protocol.DocumentURI) (*packages.Package, error) {
v.config.Overlay = v.overlay()
pkgs, err := packages.Load(v.config, fmt.Sprintf("file=%s", uriToFilename(uri)))
if len(pkgs) == 0 {
if err == nil {
err = fmt.Errorf("no packages found for %s", uri)
// TypeCheck type-checks the package for the given package path.
func (v *View) TypeCheck(uri protocol.DocumentURI) (*packages.Package, error) {
v.Config.Overlay = v.overlay()
path, err := FromURI(uri)
if err != nil {
return nil, err
}
pkgs, err := packages.Load(v.Config, fmt.Sprintf("file=%s", path))
if len(pkgs) == 0 {
return nil, err
}
pkg := pkgs[0]
return pkg, nil
}
func uriToFilename(uri protocol.DocumentURI) string {
return strings.TrimPrefix(string(uri), "file://")
}
func filenameToURI(filename string) protocol.DocumentURI {
return protocol.DocumentURI("file://" + filename)
}