Skip to content
Permalink
Browse files

Merge pull request #1 from sourcegraph/add-lsp-types-from-sourcegraph

Move LSP types from sourcegraph/sourcegraph into go-langserver
  • Loading branch information...
chrismwendt committed Nov 19, 2018
2 parents 4631ffd + 7402f69 commit 0c7d621186c1c8684f3d97868f6970b326609cc7
Showing with 326 additions and 0 deletions.
  1. +1 −0 go.mod
  2. +14 −0 lspext/context_exec.go
  3. +26 −0 lspext/fs.go
  4. +175 −0 lspext/proxy_lspext.go
  5. +63 −0 lspext/proxy_lspext_test.go
  6. +47 −0 lspext/workspace_packages.go
1 go.mod
@@ -0,0 +1 @@
module github.com/sourcegraph/go-lsp
@@ -0,0 +1,14 @@
package lspext

// ExecParams contains the parameters for the exec LSP request.
type ExecParams struct {
Command string `json:"command"`
Arguments []string `json:"arguments"`
}

// ExecResult contains the result for the exec LSP response.
type ExecResult struct {
Stdout string `json:"stdout"`
Stderr string `json:"stderr"`
ExitCode int `json:"exitCode"`
}
@@ -0,0 +1,26 @@
package lspext

import (
"os"
"path"
"time"
)

// FileInfo is the map-based implementation of FileInfo.
type FileInfo struct {
Name_ string `json:"name"`
Size_ int64 `json:"size"`
Dir_ bool `json:"dir"`
}

func (fi FileInfo) IsDir() bool { return fi.Dir_ }
func (fi FileInfo) ModTime() time.Time { return time.Time{} }
func (fi FileInfo) Mode() os.FileMode {
if fi.IsDir() {
return 0755 | os.ModeDir
}
return 0444
}
func (fi FileInfo) Name() string { return path.Base(fi.Name_) }
func (fi FileInfo) Size() int64 { return int64(fi.Size_) }
func (fi FileInfo) Sys() interface{} { return nil }
@@ -0,0 +1,175 @@
// Package lspx contains extensions to the LSP protocol.
//
// An overview of the different protocol variants:
//
// // vanilla LSP
// github.com/sourcegraph/go-langserver/pkg/lsp
//
// // proxy (http gateway) server LSP extensions
// github.com/sourcegraph/sourcegraph/xlang
//
// // (this package) build/lang server LSP extensions
// github.com/sourcegraph/sourcegraph/xlang/lspx
//
package lspext

import (
"reflect"
"time"

"github.com/sourcegraph/go-lsp"
)

// DependencyReference represents a reference to a dependency. []DependencyReference
// is the return type for the build server workspace/xdependencies method.
type DependencyReference struct {
// Attributes describing the dependency that is being referenced. It is up
// to the language server to define the schema of this object.
Attributes map[string]interface{} `json:"attributes,omitempty"`

// Hints is treated as an opaque object and passed directly by Sourcegraph
// into the language server's workspace/xreferences method to help narrow
// down the search space for the references to the symbol.
//
// If a language server emits no hints, Sourcegraph will pass none as a
// parameter to workspace/xreferences which means it must search the entire
// repository (workspace) in order to find references. workspace/xdependencies
// should emit sufficient hints here for making all workspace/xreference
// queries complete in a reasonable amount of time (less than a few seconds
// for very large repositories). For example, one may include the
// containing "package" or other build-system level "code unit". Emitting
// the exact file is not recommended in general as that would produce more
// data for little performance gain in most situations.
Hints map[string]interface{} `json:"hints,omitempty"`
}

// TelemetryEventParams is a telemetry/event message sent from a
// build/lang server back to the proxy. The information here is
// forwarded to our opentracing system.
type TelemetryEventParams struct {
Op string `json:"op"` // the operation name
StartTime time.Time `json:"startTime"` // when the operation started
EndTime time.Time `json:"endTime"` // when the operation ended
Tags map[string]string `json:"tags,omitempty"` // other metadata
}

type InitializeParams struct {
lsp.InitializeParams

// OriginalRootURI is the original rootUri for this LSP session,
// before any path rewriting occurred. It is typically a Git clone
// URL of the form
// "git://github.com/facebook/react.git?rev=master#lib".
//
// The Go lang/build server uses this to infer the import path
// root (and directory structure) to use for a workspace.
OriginalRootURI lsp.DocumentURI `json:"originalRootUri"`

// Mode is the name of the language. It is used to determine the correct
// language server to route a request to, and to inform a language server
// what languages it should contribute.
Mode string `json:"mode"`
}

// WalkURIFields walks the LSP params/result object for fields
// containing document URIs.
//
// If collect is non-nil, it calls collect(uri) for every URI
// encountered. Callers can use this to collect a list of all document
// URIs referenced in the params/result.
//
// If update is non-nil, it updates all document URIs in an LSP
// params/result with the value of f(existingURI). Callers can use
// this to rewrite paths in the params/result.
//
// TODO(sqs): does not support WorkspaceEdit (with a field whose
// TypeScript type is {[uri: string]: TextEdit[]}.
func WalkURIFields(o interface{}, collect func(lsp.DocumentURI), update func(lsp.DocumentURI) lsp.DocumentURI) {
var walk func(o interface{})
walk = func(o interface{}) {
switch o := o.(type) {
case map[string]interface{}:
for k, v := range o { // Location, TextDocumentIdentifier, TextDocumentItem, etc.
if k == "uri" {
s, ok := v.(string)
if !ok {
s2, ok2 := v.(lsp.DocumentURI)
s = string(s2)
ok = ok2
}
if ok {
if collect != nil {
collect(lsp.DocumentURI(s))
}
if update != nil {
o[k] = update(lsp.DocumentURI(s))
}
continue
}
}
walk(v)
}
case []interface{}: // Location[]
for _, v := range o {
walk(v)
}
default: // structs with a "URI" field
rv := reflect.ValueOf(o)
if rv.Kind() == reflect.Ptr {
rv = rv.Elem()
}
if rv.Kind() == reflect.Struct {
if fv := rv.FieldByName("URI"); fv.Kind() == reflect.String {
if collect != nil {
collect(lsp.DocumentURI(fv.String()))
}
if update != nil {
fv.SetString(string(update(lsp.DocumentURI(fv.String()))))
}
}
for i := 0; i < rv.NumField(); i++ {
fv := rv.Field(i)
if fv.Kind() == reflect.Ptr || fv.Kind() == reflect.Struct || fv.Kind() == reflect.Array {
walk(fv.Interface())
}
}
}
}
}
walk(o)
}

// ClientProxyInitializeParams are sent by the client to the proxy in
// the "initialize" request. It has a non-standard field "mode", which
// is the name of the language (using vscode terminology); "go" or
// "typescript", for example.
type ClientProxyInitializeParams struct {
lsp.InitializeParams
InitializationOptions ClientProxyInitializationOptions `json:"initializationOptions"`

// Mode is DEPRECATED; it was moved to the subfield
// initializationOptions.Mode. It is still here for backward
// compatibility until the xlang service is upgraded.
Mode string `json:"mode,omitempty"`
}

// ClientProxyInitializationOptions is the "initializationOptions"
// field of the "initialize" request params sent from the client to
// the LSP client proxy.
type ClientProxyInitializationOptions struct {
Mode string `json:"mode"`

// Same as, but takes precedence over, InitializeParams.rootUri.
// vscode-languageserver-node's LanguageClient doesn't allow overriding
// InitializeParams.rootUri, so clients that use vscode-languageserver-node
// instead set InitializeParams.initializationOptions.rootUri.
RootURI *lsp.DocumentURI `json:"rootUri,omitempty"`

// Session, if set, causes this session to be isolated from other
// LSP sessions using the same workspace and mode. See
// (contextID).session for more information.
Session string `json:"session,omitempty"`

// ZipURL is the zip URL to forward to the language server.
ZipURL string `json:"zipURL,omitempty"`
}
@@ -0,0 +1,63 @@
package lspext

import (
"encoding/json"
"reflect"
"strings"
"testing"

"github.com/sourcegraph/go-lsp"
)

func TestWalkURIFields(t *testing.T) {
tests := map[string][]lsp.DocumentURI{
`{"textDocument":{"uri":"u1"}}`: []lsp.DocumentURI{"u1"},
`{"uri":"u1"}`: []lsp.DocumentURI{"u1"},
}
for objStr, wantURIs := range tests {
var obj interface{}
if err := json.Unmarshal([]byte(objStr), &obj); err != nil {
t.Error(err)
continue
}

var uris []lsp.DocumentURI
collect := func(uri lsp.DocumentURI) { uris = append(uris, uri) }
update := func(uri lsp.DocumentURI) lsp.DocumentURI { return "XXX" }
WalkURIFields(obj, collect, update)

if !reflect.DeepEqual(uris, wantURIs) {
t.Errorf("%s: got URIs %q, want %q", objStr, uris, wantURIs)
}

wantObj := objStr
for _, uri := range uris {
wantObj = strings.Replace(wantObj, string(uri), "XXX", -1)
}
gotObj, err := json.Marshal(obj)
if err != nil {
t.Error(err)
continue
}
if string(gotObj) != wantObj {
t.Errorf("%s: got obj %q, want %q after updating URI pointers", objStr, gotObj, wantObj)
}
}
}

func TestWalkURIFields_struct(t *testing.T) {
v := lsp.PublishDiagnosticsParams{URI: "u1"}

var uris []lsp.DocumentURI
collect := func(uri lsp.DocumentURI) { uris = append(uris, uri) }
update := func(uri lsp.DocumentURI) lsp.DocumentURI { return "XXX" }
WalkURIFields(&v, collect, update)

if want := []lsp.DocumentURI{"u1"}; !reflect.DeepEqual(uris, want) {
t.Errorf("got %v, want %v", uris, want)
}

if want := "XXX"; string(v.URI) != want {
t.Errorf("got %q, want %q", v.URI, want)
}
}
@@ -0,0 +1,47 @@
package lspext

import (
"fmt"
"sort"
"strings"
)

// WorkspacePackagesParams is parameters for the `workspace/xpackages` extension.
//
// See: https://github.com/sourcegraph/language-server-protocol/blob/7fd3c1/extension-workspace-references.md
type WorkspacePackagesParams struct{}

// PackageInformation is the metadata associated with a build-system-
// or package-manager-level package. Sometimes, languages have
// abstractions called "packages" as well, but this refers
// specifically to packages as defined by the build system or package
// manager. E.g., Python pip packages (NOT Python language packages or
// modules), Go packages, Maven packages (NOT Java language packages),
// npm modules (NOT JavaScript language modules). PackageInformation
// includes both attributes of the package itself and attributes of
// the package's dependencies.
type PackageInformation struct {
// Package is the set of attributes of the package
Package PackageDescriptor `json:"package,omitempty"`

// Dependencies is the list of dependency attributes
Dependencies []DependencyReference `json:"dependencies,omitempty"`
}

// PackageDescriptor identifies a package (usually but not always uniquely).
type PackageDescriptor map[string]interface{}

// String returns a consistently ordered string representation of the
// PackageDescriptor. It is useful for testing.
func (s PackageDescriptor) String() string {
sm := make(sortedMap, 0, len(s))
for k, v := range s {
sm = append(sm, mapValue{key: k, value: v})
}
sort.Sort(sm)
var str string
for _, v := range sm {
str += fmt.Sprintf("%s:%v ", v.key, v.value)
}
return strings.TrimSpace(str)
}

0 comments on commit 0c7d621

Please sign in to comment.
You can’t perform that action at this time.