156 lines
4.0 KiB
Go
156 lines
4.0 KiB
Go
package gopls
|
|
|
|
import (
|
|
"os/exec"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"context"
|
|
|
|
"go.lsp.dev/jsonrpc2"
|
|
"go.lsp.dev/protocol"
|
|
|
|
"time"
|
|
|
|
"go.lsp.dev/uri"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
type Connection struct {
|
|
logger *zap.Logger
|
|
server protocol.Server
|
|
cmd *exec.Cmd
|
|
conn jsonrpc2.Conn
|
|
}
|
|
|
|
func NewGoplsClient(ctx context.Context, logger *zap.Logger, rootDir string) *Connection {
|
|
var err error
|
|
|
|
logger.Debug("initializing gopls client")
|
|
|
|
gopls := &Connection{
|
|
logger: logger,
|
|
}
|
|
|
|
client := NewDummyClient(logger)
|
|
|
|
// Step 1: Create a JSON-RPC connection using stdin and stdout
|
|
gopls.cmd = exec.Command("gopls", "serve")
|
|
|
|
stdin, err := gopls.cmd.StdinPipe()
|
|
if err != nil {
|
|
logger.Error("Failed to get stdin pipe", zap.Error(err))
|
|
panic(err)
|
|
}
|
|
|
|
stdout, err := gopls.cmd.StdoutPipe()
|
|
if err != nil {
|
|
logger.Error("Failed to get stdout pipe", zap.Error(err))
|
|
panic(err)
|
|
}
|
|
|
|
err = gopls.cmd.Start()
|
|
if err != nil {
|
|
logger.Error("Failed to start gopls", zap.Error(err))
|
|
panic(err)
|
|
}
|
|
|
|
stream := jsonrpc2.NewStream(&IOStream{
|
|
stdin: stdin,
|
|
stdout: stdout,
|
|
})
|
|
|
|
// Step 2: Create a client for the running gopls server
|
|
ctx, gopls.conn, gopls.server = protocol.NewClient(ctx, client, stream, logger)
|
|
|
|
// Step 3: Initialize the gopls server
|
|
initParams := protocol.InitializeParams{
|
|
RootURI: uri.From("file", "", rootDir, "", ""),
|
|
InitializationOptions: map[string]interface{}{
|
|
"symbolMatcher": "FastFuzzy",
|
|
},
|
|
}
|
|
|
|
_, err = gopls.server.Initialize(ctx, &initParams)
|
|
if err != nil {
|
|
logger.Error("Error during initialize", zap.Error(err))
|
|
panic(err)
|
|
}
|
|
|
|
// Step 4: Send 'initialized' notification
|
|
err = gopls.server.Initialized(ctx, &protocol.InitializedParams{})
|
|
if err != nil {
|
|
logger.Error("Error during initialized", zap.Error(err))
|
|
panic(err)
|
|
}
|
|
|
|
return gopls
|
|
}
|
|
|
|
func (gopls *Connection) Definition(ctx context.Context, filePath string, lineNumber int, charPosition int) (string, int, error) {
|
|
// NOTE: gopls uses 0-based line and column numbers
|
|
defFile, defLine, err := gopls.definition(ctx, filePath, lineNumber-1, charPosition-1)
|
|
return defFile, defLine + 1, err
|
|
}
|
|
|
|
func (gopls *Connection) definition(ctx context.Context, filePath string, lineNumber int, charPosition int) (string, int, error) {
|
|
// Define the file URI and position where the function/method is invoked
|
|
fileURI := protocol.DocumentURI("file://" + filePath) // Replace with actual file URI
|
|
line := lineNumber // Line number where the function is called
|
|
character := charPosition // Character (column) where the function is called
|
|
|
|
// Send the definition request
|
|
params := &protocol.DefinitionParams{
|
|
TextDocumentPositionParams: protocol.TextDocumentPositionParams{
|
|
TextDocument: protocol.TextDocumentIdentifier{
|
|
URI: fileURI,
|
|
},
|
|
Position: protocol.Position{
|
|
Line: uint32(line),
|
|
Character: uint32(character),
|
|
},
|
|
},
|
|
}
|
|
|
|
// Create context with a timeout to avoid hanging
|
|
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
|
defer cancel()
|
|
|
|
locations, err := gopls.server.Definition(ctx, params)
|
|
if err != nil {
|
|
return "", 0, errors.Wrap(err, "failed to fetch definition")
|
|
}
|
|
|
|
if len(locations) == 0 {
|
|
return "", 0, errors.New("no definition found")
|
|
}
|
|
|
|
location := locations[0]
|
|
return location.URI.Filename(), int(location.Range.Start.Line), nil
|
|
}
|
|
|
|
func (gopls *Connection) DidOpen(ctx context.Context, path string, content string, logger *zap.Logger) {
|
|
err := gopls.server.DidOpen(ctx, &protocol.DidOpenTextDocumentParams{
|
|
TextDocument: protocol.TextDocumentItem{
|
|
URI: protocol.DocumentURI(path),
|
|
LanguageID: "go",
|
|
Version: 1,
|
|
Text: content,
|
|
},
|
|
})
|
|
if err != nil {
|
|
logger.Error("failed to call DidOpen", zap.Error(err))
|
|
}
|
|
}
|
|
|
|
func (gopls *Connection) DidClose(ctx context.Context, path string, lgoger *zap.Logger) {
|
|
err := gopls.server.DidClose(ctx, &protocol.DidCloseTextDocumentParams{
|
|
TextDocument: protocol.TextDocumentIdentifier{
|
|
URI: protocol.DocumentURI(path),
|
|
},
|
|
})
|
|
if err != nil {
|
|
lgoger.Error("failed to call DidClose", zap.Error(err))
|
|
}
|
|
}
|