refactor: items 8-10, 12, 13 from code review
- Move parseRetryAfter to backend.go (where it's called) - Extract paths.RuntimeDir() replacing 3 divergent XDG_RUNTIME_DIR impls - Rename CodeWhispererBackend → KiroBackend, NewCodeWhisperer → NewKiro - Export RPCRequest/RPCError/OutputNotification from toolsrv/rpcwire.go - Factor tools/lsp/cmdutil: Run() and RunCustom() for 7 LSP binaries
This commit is contained in:
parent
9c90cb8954
commit
74635b8e00
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
@ -240,3 +241,22 @@ func isContextOverflow(body string) bool {
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseRetryAfter parses the Retry-After header value, which may be an integer
|
||||||
|
// number of seconds or an HTTP-date. Returns zero if the header is absent or
|
||||||
|
// unparseable.
|
||||||
|
func parseRetryAfter(header string) time.Duration {
|
||||||
|
if header == "" {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
header = strings.TrimSpace(header)
|
||||||
|
if secs, err := strconv.Atoi(header); err == nil {
|
||||||
|
return time.Duration(secs) * time.Second
|
||||||
|
}
|
||||||
|
if t, err := http.ParseTime(header); err == nil {
|
||||||
|
if d := time.Until(t); d > 0 {
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
package backend
|
package backend
|
||||||
|
|
||||||
// CodeWhispererBackend implements Backend for Amazon CodeWhisperer / Kiro.
|
// KiroBackend implements Backend for the Kiro streaming API.
|
||||||
//
|
//
|
||||||
// COVERAGE: Intentionally untested. Reverse-engineered Kiro streaming API
|
// COVERAGE: Intentionally untested. Reverse-engineered Kiro streaming API
|
||||||
// client requiring a live session to test.
|
// client requiring a live session to test.
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
// Auth is configured via the apiKey parameter to NewCodeWhisperer:
|
// Auth is configured via the apiKey parameter to NewKiro:
|
||||||
// - Empty string → read from Kiro CLI SQLite database at the default path
|
// - Empty string → read from Kiro CLI SQLite database at the default path
|
||||||
// ($XDG_DATA_HOME/kiro-cli/data.sqlite3 on Linux).
|
// ($XDG_DATA_HOME/kiro-cli/data.sqlite3 on Linux).
|
||||||
// - "sqlite:///path/to/data.sqlite3" → read from the specified SQLite file.
|
// - "sqlite:///path/to/data.sqlite3" → read from the specified SQLite file.
|
||||||
|
|
@ -35,7 +35,7 @@ import (
|
||||||
|
|
||||||
// ── Public constructor ────────────────────────────────────────────────────────
|
// ── Public constructor ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
type CodeWhispererBackend struct {
|
type KiroBackend struct {
|
||||||
baseBackend
|
baseBackend
|
||||||
endpoint string // overrides auth-derived endpoint if non-empty
|
endpoint string // overrides auth-derived endpoint if non-empty
|
||||||
extraHeaders map[string]string
|
extraHeaders map[string]string
|
||||||
|
|
@ -49,13 +49,13 @@ type CodeWhispererBackend struct {
|
||||||
modelsCacheAt time.Time
|
modelsCacheAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCodeWhisperer returns a CodeWhisperer backend. See package doc for apiKey semantics.
|
// NewKiro returns a Kiro backend. See package doc for apiKey semantics.
|
||||||
func NewCodeWhisperer(apiKey string) (*CodeWhispererBackend, error) {
|
func NewKiro(apiKey string) (*KiroBackend, error) {
|
||||||
if strings.TrimSpace(apiKey) == "" {
|
if strings.TrimSpace(apiKey) == "" {
|
||||||
apiKey = kiroDefaultSQLiteKey()
|
apiKey = kiroDefaultSQLiteKey()
|
||||||
}
|
}
|
||||||
authSource, err := newKiroAuthSource(apiKey)
|
authSource, err := newKiroAuthSource(apiKey)
|
||||||
b := &CodeWhispererBackend{
|
b := &KiroBackend{
|
||||||
baseBackend: baseBackend{name: "kiro"},
|
baseBackend: baseBackend{name: "kiro"},
|
||||||
authSource: authSource,
|
authSource: authSource,
|
||||||
authInitErr: err,
|
authInitErr: err,
|
||||||
|
|
@ -65,9 +65,9 @@ func NewCodeWhisperer(apiKey string) (*CodeWhispererBackend, error) {
|
||||||
return b, nil
|
return b, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *CodeWhispererBackend) DefaultModel() string { return "auto" }
|
func (b *KiroBackend) DefaultModel() string { return "auto" }
|
||||||
|
|
||||||
func (b *CodeWhispererBackend) Models(ctx context.Context) []string {
|
func (b *KiroBackend) Models(ctx context.Context) []string {
|
||||||
b.modelsMu.Lock()
|
b.modelsMu.Lock()
|
||||||
if b.modelsCache != nil && time.Since(b.modelsCacheAt) < 24*time.Hour {
|
if b.modelsCache != nil && time.Since(b.modelsCacheAt) < 24*time.Hour {
|
||||||
result := b.modelsCache
|
result := b.modelsCache
|
||||||
|
|
@ -92,7 +92,7 @@ func (b *CodeWhispererBackend) Models(ctx context.Context) []string {
|
||||||
return ids
|
return ids
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *CodeWhispererBackend) fetchModels(ctx context.Context) *kiroListModelsResponse {
|
func (b *KiroBackend) fetchModels(ctx context.Context) *kiroListModelsResponse {
|
||||||
if b.authInitErr != nil {
|
if b.authInitErr != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -113,7 +113,7 @@ func (b *CodeWhispererBackend) fetchModels(ctx context.Context) *kiroListModelsR
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *CodeWhispererBackend) ContextLength(ctx context.Context) int {
|
func (b *KiroBackend) ContextLength(ctx context.Context) int {
|
||||||
if b.ctxLen > 0 {
|
if b.ctxLen > 0 {
|
||||||
return b.ctxLen
|
return b.ctxLen
|
||||||
}
|
}
|
||||||
|
|
@ -136,7 +136,7 @@ func (b *CodeWhispererBackend) ContextLength(ctx context.Context) int {
|
||||||
|
|
||||||
// ── Backend interface ─────────────────────────────────────────────────────────
|
// ── Backend interface ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
func (b *CodeWhispererBackend) ChatStream(
|
func (b *KiroBackend) ChatStream(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
messages []Message,
|
messages []Message,
|
||||||
tools []Tool,
|
tools []Tool,
|
||||||
|
|
@ -172,7 +172,7 @@ const kiroMaxThrottleRetries = 5
|
||||||
// Tests can override this.
|
// Tests can override this.
|
||||||
var kiroThrottleBaseDelay = 5 * time.Second
|
var kiroThrottleBaseDelay = 5 * time.Second
|
||||||
|
|
||||||
func (b *CodeWhispererBackend) runStream(ctx context.Context, req *kiroGenerateRequest, ch chan<- StreamEvent) {
|
func (b *KiroBackend) runStream(ctx context.Context, req *kiroGenerateRequest, ch chan<- StreamEvent) {
|
||||||
var throttleAttempts int
|
var throttleAttempts int
|
||||||
for {
|
for {
|
||||||
token, err := b.authSource.AccessToken(ctx)
|
token, err := b.authSource.AccessToken(ctx)
|
||||||
|
|
@ -258,14 +258,14 @@ func (b *CodeWhispererBackend) runStream(ctx context.Context, req *kiroGenerateR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *CodeWhispererBackend) resolveEndpoint(ctx context.Context) (string, error) {
|
func (b *KiroBackend) resolveEndpoint(ctx context.Context) (string, error) {
|
||||||
if ep := strings.TrimSpace(b.endpoint); ep != "" {
|
if ep := strings.TrimSpace(b.endpoint); ep != "" {
|
||||||
return ep, nil
|
return ep, nil
|
||||||
}
|
}
|
||||||
return b.authSource.DefaultEndpoint(ctx)
|
return b.authSource.DefaultEndpoint(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *CodeWhispererBackend) shouldRefresh(err error) bool {
|
func (b *KiroBackend) shouldRefresh(err error) bool {
|
||||||
if !b.authSource.CanRefresh() {
|
if !b.authSource.CanRefresh() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package backend
|
package backend
|
||||||
|
|
||||||
// Low-level plumbing for the Amazon CodeWhisperer / Kiro backend.
|
// Low-level plumbing for the Kiro backend.
|
||||||
// COVERAGE: Intentionally untested. See codewhisperer.go.
|
// COVERAGE: Intentionally untested. See codewhisperer.go.
|
||||||
//
|
//
|
||||||
// - Wire types for the GenerateAssistantResponse API
|
// - Wire types for the GenerateAssistantResponse API
|
||||||
|
|
@ -95,7 +95,7 @@ func newBackendFromConfig(which string, cfg configFile) (Backend, error) {
|
||||||
if token == "" {
|
if token == "" {
|
||||||
token = os.Getenv("OLLIE_KIRO_TOKEN")
|
token = os.Getenv("OLLIE_KIRO_TOKEN")
|
||||||
}
|
}
|
||||||
b, err = NewCodeWhisperer(token)
|
b, err = NewKiro(token)
|
||||||
|
|
||||||
case "gemini":
|
case "gemini":
|
||||||
key := bc.Key
|
key := bc.Key
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// OpenAIBackend speaks the OpenAI /v1/chat/completions wire format.
|
// OpenAIBackend speaks the OpenAI /v1/chat/completions wire format.
|
||||||
|
|
@ -549,21 +547,3 @@ func (b *OpenAIBackend) ChatStream(ctx context.Context, messages []Message, tool
|
||||||
return streamRequest(b.client, httpReq, "openai", streamOpenAISSE)
|
return streamRequest(b.client, httpReq, "openai", streamOpenAISSE)
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseRetryAfter parses the Retry-After header value, which may be an integer
|
|
||||||
// number of seconds or an HTTP-date. Returns zero if the header is absent or
|
|
||||||
// unparseable.
|
|
||||||
func parseRetryAfter(header string) time.Duration {
|
|
||||||
if header == "" {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
header = strings.TrimSpace(header)
|
|
||||||
if secs, err := strconv.Atoi(header); err == nil {
|
|
||||||
return time.Duration(secs) * time.Second
|
|
||||||
}
|
|
||||||
if t, err := http.ParseTime(header); err == nil {
|
|
||||||
if d := time.Until(t); d > 0 {
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -158,30 +158,14 @@ func serveSocket(ctx context.Context, srv *toolsrv.Server, sockPath string) {
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
type rpcRequest struct {
|
|
||||||
JSONRPC string `json:"jsonrpc"`
|
|
||||||
ID json.RawMessage `json:"id"`
|
|
||||||
Method string `json:"method"`
|
|
||||||
Params json.RawMessage `json:"params"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type rpcResponse struct {
|
type rpcResponse struct {
|
||||||
JSONRPC string `json:"jsonrpc"`
|
JSONRPC string `json:"jsonrpc"`
|
||||||
ID json.RawMessage `json:"id"`
|
ID json.RawMessage `json:"id"`
|
||||||
Result any `json:"result,omitempty"`
|
Result any `json:"result,omitempty"`
|
||||||
Error *rpcError `json:"error,omitempty"`
|
Error *toolsrv.RPCError `json:"error,omitempty"`
|
||||||
Stream bool `json:"stream,omitempty"`
|
Stream bool `json:"stream,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type rpcError struct {
|
|
||||||
Code int `json:"code"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type outputNotif struct {
|
|
||||||
Data string `json:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type lockedEncoder struct {
|
type lockedEncoder struct {
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
enc *json.Encoder
|
enc *json.Encoder
|
||||||
|
|
@ -198,7 +182,7 @@ func serveRPC(ctx context.Context, srv *toolsrv.Server, in io.Reader, out io.Wri
|
||||||
enc := &lockedEncoder{enc: json.NewEncoder(out)}
|
enc := &lockedEncoder{enc: json.NewEncoder(out)}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
var req rpcRequest
|
var req toolsrv.RPCRequest
|
||||||
if err := dec.Decode(&req); err != nil {
|
if err := dec.Decode(&req); err != nil {
|
||||||
if err == io.EOF || ctx.Err() != nil {
|
if err == io.EOF || ctx.Err() != nil {
|
||||||
return
|
return
|
||||||
|
|
@ -206,7 +190,7 @@ func serveRPC(ctx context.Context, srv *toolsrv.Server, in io.Reader, out io.Wri
|
||||||
enc.Encode(rpcResponse{
|
enc.Encode(rpcResponse{
|
||||||
JSONRPC: "2.0",
|
JSONRPC: "2.0",
|
||||||
ID: req.ID,
|
ID: req.ID,
|
||||||
Error: &rpcError{Code: -32700, Message: "parse error: " + err.Error()},
|
Error: &toolsrv.RPCError{Code: -32700, Message: "parse error: " + err.Error()},
|
||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -215,7 +199,7 @@ func serveRPC(ctx context.Context, srv *toolsrv.Server, in io.Reader, out io.Wri
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleRPC(ctx context.Context, srv *toolsrv.Server, req rpcRequest, enc *lockedEncoder) {
|
func handleRPC(ctx context.Context, srv *toolsrv.Server, req toolsrv.RPCRequest, enc *lockedEncoder) {
|
||||||
switch req.Method {
|
switch req.Method {
|
||||||
case "list_tools":
|
case "list_tools":
|
||||||
tools, err := srv.ListTools()
|
tools, err := srv.ListTools()
|
||||||
|
|
@ -223,7 +207,7 @@ func handleRPC(ctx context.Context, srv *toolsrv.Server, req rpcRequest, enc *lo
|
||||||
enc.Encode(rpcResponse{
|
enc.Encode(rpcResponse{
|
||||||
JSONRPC: "2.0",
|
JSONRPC: "2.0",
|
||||||
ID: req.ID,
|
ID: req.ID,
|
||||||
Error: &rpcError{Code: -32000, Message: err.Error()},
|
Error: &toolsrv.RPCError{Code: -32000, Message: err.Error()},
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
enc.Encode(rpcResponse{
|
enc.Encode(rpcResponse{
|
||||||
|
|
@ -258,7 +242,7 @@ func handleRPC(ctx context.Context, srv *toolsrv.Server, req rpcRequest, enc *lo
|
||||||
Value string `json:"value"`
|
Value string `json:"value"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(req.Params, ¶ms); err != nil {
|
if err := json.Unmarshal(req.Params, ¶ms); err != nil {
|
||||||
enc.Encode(rpcResponse{JSONRPC: "2.0", ID: req.ID, Error: &rpcError{Code: -32602, Message: err.Error()}})
|
enc.Encode(rpcResponse{JSONRPC: "2.0", ID: req.ID, Error: &toolsrv.RPCError{Code: -32602, Message: err.Error()}})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
srv.SetEnv(params.Key, params.Value)
|
srv.SetEnv(params.Key, params.Value)
|
||||||
|
|
@ -269,7 +253,7 @@ func handleRPC(ctx context.Context, srv *toolsrv.Server, req rpcRequest, enc *lo
|
||||||
Dir string `json:"dir"`
|
Dir string `json:"dir"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(req.Params, ¶ms); err != nil {
|
if err := json.Unmarshal(req.Params, ¶ms); err != nil {
|
||||||
enc.Encode(rpcResponse{JSONRPC: "2.0", ID: req.ID, Error: &rpcError{Code: -32602, Message: err.Error()}})
|
enc.Encode(rpcResponse{JSONRPC: "2.0", ID: req.ID, Error: &toolsrv.RPCError{Code: -32602, Message: err.Error()}})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
srv.SetCWD(params.Dir)
|
srv.SetCWD(params.Dir)
|
||||||
|
|
@ -280,7 +264,7 @@ func handleRPC(ctx context.Context, srv *toolsrv.Server, req rpcRequest, enc *lo
|
||||||
Names []string `json:"names"`
|
Names []string `json:"names"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(req.Params, ¶ms); err != nil {
|
if err := json.Unmarshal(req.Params, ¶ms); err != nil {
|
||||||
enc.Encode(rpcResponse{JSONRPC: "2.0", ID: req.ID, Error: &rpcError{Code: -32602, Message: err.Error()}})
|
enc.Encode(rpcResponse{JSONRPC: "2.0", ID: req.ID, Error: &toolsrv.RPCError{Code: -32602, Message: err.Error()}})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
srv.SetAllowTools(params.Names)
|
srv.SetAllowTools(params.Names)
|
||||||
|
|
@ -299,11 +283,11 @@ func handleRPC(ctx context.Context, srv *toolsrv.Server, req rpcRequest, enc *lo
|
||||||
Signal int `json:"signal"`
|
Signal int `json:"signal"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(req.Params, ¶ms); err != nil {
|
if err := json.Unmarshal(req.Params, ¶ms); err != nil {
|
||||||
enc.Encode(rpcResponse{JSONRPC: "2.0", ID: req.ID, Error: &rpcError{Code: -32602, Message: err.Error()}})
|
enc.Encode(rpcResponse{JSONRPC: "2.0", ID: req.ID, Error: &toolsrv.RPCError{Code: -32602, Message: err.Error()}})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := srv.SignalDetached(params.PID, syscall.Signal(params.Signal)); err != nil {
|
if err := srv.SignalDetached(params.PID, syscall.Signal(params.Signal)); err != nil {
|
||||||
enc.Encode(rpcResponse{JSONRPC: "2.0", ID: req.ID, Error: &rpcError{Code: -32000, Message: err.Error()}})
|
enc.Encode(rpcResponse{JSONRPC: "2.0", ID: req.ID, Error: &toolsrv.RPCError{Code: -32000, Message: err.Error()}})
|
||||||
} else {
|
} else {
|
||||||
enc.Encode(rpcResponse{JSONRPC: "2.0", ID: req.ID, Result: true})
|
enc.Encode(rpcResponse{JSONRPC: "2.0", ID: req.ID, Result: true})
|
||||||
}
|
}
|
||||||
|
|
@ -313,12 +297,12 @@ func handleRPC(ctx context.Context, srv *toolsrv.Server, req rpcRequest, enc *lo
|
||||||
PID int `json:"pid"`
|
PID int `json:"pid"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(req.Params, ¶ms); err != nil {
|
if err := json.Unmarshal(req.Params, ¶ms); err != nil {
|
||||||
enc.Encode(rpcResponse{JSONRPC: "2.0", ID: req.ID, Error: &rpcError{Code: -32602, Message: err.Error()}})
|
enc.Encode(rpcResponse{JSONRPC: "2.0", ID: req.ID, Error: &toolsrv.RPCError{Code: -32602, Message: err.Error()}})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
out, err := srv.GetDetachedOutput(params.PID)
|
out, err := srv.GetDetachedOutput(params.PID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
enc.Encode(rpcResponse{JSONRPC: "2.0", ID: req.ID, Error: &rpcError{Code: -32000, Message: err.Error()}})
|
enc.Encode(rpcResponse{JSONRPC: "2.0", ID: req.ID, Error: &toolsrv.RPCError{Code: -32000, Message: err.Error()}})
|
||||||
} else {
|
} else {
|
||||||
enc.Encode(rpcResponse{JSONRPC: "2.0", ID: req.ID, Result: out})
|
enc.Encode(rpcResponse{JSONRPC: "2.0", ID: req.ID, Result: out})
|
||||||
}
|
}
|
||||||
|
|
@ -328,7 +312,7 @@ func handleRPC(ctx context.Context, srv *toolsrv.Server, req rpcRequest, enc *lo
|
||||||
PID int `json:"pid"`
|
PID int `json:"pid"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(req.Params, ¶ms); err != nil {
|
if err := json.Unmarshal(req.Params, ¶ms); err != nil {
|
||||||
enc.Encode(rpcResponse{JSONRPC: "2.0", ID: req.ID, Error: &rpcError{Code: -32602, Message: err.Error()}})
|
enc.Encode(rpcResponse{JSONRPC: "2.0", ID: req.ID, Error: &toolsrv.RPCError{Code: -32602, Message: err.Error()}})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ok := srv.DismissDetached(params.PID)
|
ok := srv.DismissDetached(params.PID)
|
||||||
|
|
@ -339,11 +323,11 @@ func handleRPC(ctx context.Context, srv *toolsrv.Server, req rpcRequest, enc *lo
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(req.Params, ¶ms); err != nil {
|
if err := json.Unmarshal(req.Params, ¶ms); err != nil {
|
||||||
enc.Encode(rpcResponse{JSONRPC: "2.0", ID: req.ID, Error: &rpcError{Code: -32602, Message: err.Error()}})
|
enc.Encode(rpcResponse{JSONRPC: "2.0", ID: req.ID, Error: &toolsrv.RPCError{Code: -32602, Message: err.Error()}})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := srv.LoadTool(params.Name); err != nil {
|
if err := srv.LoadTool(params.Name); err != nil {
|
||||||
enc.Encode(rpcResponse{JSONRPC: "2.0", ID: req.ID, Error: &rpcError{Code: -32000, Message: err.Error()}})
|
enc.Encode(rpcResponse{JSONRPC: "2.0", ID: req.ID, Error: &toolsrv.RPCError{Code: -32000, Message: err.Error()}})
|
||||||
} else {
|
} else {
|
||||||
res, _ := json.Marshal(map[string]string{"result": "loaded: " + params.Name})
|
res, _ := json.Marshal(map[string]string{"result": "loaded: " + params.Name})
|
||||||
enc.Encode(rpcResponse{JSONRPC: "2.0", ID: req.ID, Result: json.RawMessage(res)})
|
enc.Encode(rpcResponse{JSONRPC: "2.0", ID: req.ID, Result: json.RawMessage(res)})
|
||||||
|
|
@ -354,7 +338,7 @@ func handleRPC(ctx context.Context, srv *toolsrv.Server, req rpcRequest, enc *lo
|
||||||
enc.Encode(rpcResponse{
|
enc.Encode(rpcResponse{
|
||||||
JSONRPC: "2.0",
|
JSONRPC: "2.0",
|
||||||
ID: req.ID,
|
ID: req.ID,
|
||||||
Result: outputNotif{Data: data},
|
Result: toolsrv.OutputNotification{Data: data},
|
||||||
Stream: true,
|
Stream: true,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
@ -363,7 +347,7 @@ func handleRPC(ctx context.Context, srv *toolsrv.Server, req rpcRequest, enc *lo
|
||||||
enc.Encode(rpcResponse{
|
enc.Encode(rpcResponse{
|
||||||
JSONRPC: "2.0",
|
JSONRPC: "2.0",
|
||||||
ID: req.ID,
|
ID: req.ID,
|
||||||
Error: &rpcError{Code: -32000, Message: err.Error()},
|
Error: &toolsrv.RPCError{Code: -32000, Message: err.Error()},
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
enc.Encode(rpcResponse{
|
enc.Encode(rpcResponse{
|
||||||
|
|
|
||||||
|
|
@ -84,11 +84,7 @@ func runServer(sockPath string) {
|
||||||
// Server creation deferred until after bypass broker is ready (see below)
|
// Server creation deferred until after bypass broker is ready (see below)
|
||||||
|
|
||||||
// Start bypass broker
|
// Start bypass broker
|
||||||
xdg := os.Getenv("XDG_RUNTIME_DIR")
|
bypassSocket := filepath.Join(paths.RuntimeDir(), "ollie", "bypass.sock")
|
||||||
if xdg == "" {
|
|
||||||
xdg = fmt.Sprintf("/run/user/%d", os.Getuid())
|
|
||||||
}
|
|
||||||
bypassSocket := filepath.Join(xdg, "ollie", "bypass.sock")
|
|
||||||
os.Setenv("OLLIE_BYPASS_SOCKET", bypassSocket) //nolint:errcheck
|
os.Setenv("OLLIE_BYPASS_SOCKET", bypassSocket) //nolint:errcheck
|
||||||
policyPath := filepath.Join(paths.DataDir(), "bypass-policy.yaml")
|
policyPath := filepath.Join(paths.DataDir(), "bypass-policy.yaml")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package paths
|
package paths
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -16,6 +17,14 @@ func ExpandHome(path string) string {
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RuntimeDir returns $XDG_RUNTIME_DIR or the standard fallback /run/user/UID.
|
||||||
|
func RuntimeDir() string {
|
||||||
|
if d := os.Getenv("XDG_RUNTIME_DIR"); d != "" {
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("/run/user/%d", os.Getuid())
|
||||||
|
}
|
||||||
|
|
||||||
// xdgConfigHome returns $XDG_CONFIG_HOME or ~/.config.
|
// xdgConfigHome returns $XDG_CONFIG_HOME or ~/.config.
|
||||||
func xdgConfigHome() string {
|
func xdgConfigHome() string {
|
||||||
if p := os.Getenv("XDG_CONFIG_HOME"); p != "" {
|
if p := os.Getenv("XDG_CONFIG_HOME"); p != "" {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
package lsp
|
package lsp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"ollie/paths"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Adapter describes an LSP server for a specific language.
|
// Adapter describes an LSP server for a specific language.
|
||||||
|
|
@ -108,11 +109,7 @@ func LanguageID(path string) string {
|
||||||
|
|
||||||
// SockPath returns the path to the bridge Unix socket.
|
// SockPath returns the path to the bridge Unix socket.
|
||||||
func SockPath() string {
|
func SockPath() string {
|
||||||
xdg := os.Getenv("XDG_RUNTIME_DIR")
|
return filepath.Join(paths.RuntimeDir(), "ollie-lsp.sock")
|
||||||
if xdg == "" {
|
|
||||||
xdg = "/tmp"
|
|
||||||
}
|
|
||||||
return filepath.Join(xdg, "ollie-lsp.sock")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PidPath returns the path to the bridge PID file.
|
// PidPath returns the path to the bridge PID file.
|
||||||
|
|
|
||||||
|
|
@ -4,69 +4,32 @@ package main
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"ollie/tools/lsp"
|
"ollie/tools/lsp/cmdutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if len(os.Args) > 1 && os.Args[1] == "--bridge" {
|
cmdutil.Run("completion", func(result json.RawMessage) {
|
||||||
lsp.RunBridge()
|
var list struct {
|
||||||
return
|
Items []completionItem `json:"items"`
|
||||||
}
|
}
|
||||||
|
if json.Unmarshal(result, &list) == nil && len(list.Items) > 0 {
|
||||||
|
for _, item := range list.Items {
|
||||||
|
printItem(item)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var args struct {
|
var items []completionItem
|
||||||
Path string `json:"path"`
|
if json.Unmarshal(result, &items) == nil {
|
||||||
Line string `json:"line"`
|
for _, item := range items {
|
||||||
Col string `json:"col"`
|
printItem(item)
|
||||||
}
|
}
|
||||||
if err := json.NewDecoder(os.Stdin).Decode(&args); err != nil {
|
return
|
||||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
}
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
file, _ := filepath.Abs(args.Path)
|
|
||||||
line, _ := strconv.Atoi(args.Line)
|
|
||||||
col, _ := strconv.Atoi(args.Col)
|
|
||||||
|
|
||||||
result, err := lsp.ClientRequest(&lsp.BridgeRequest{
|
|
||||||
Method: "completion",
|
|
||||||
File: file,
|
|
||||||
Line: line - 1,
|
|
||||||
Col: col - 1,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if result == nil || string(result) == "null" {
|
|
||||||
fmt.Println("(no completions)")
|
fmt.Println("(no completions)")
|
||||||
return
|
})
|
||||||
}
|
|
||||||
|
|
||||||
// CompletionList or []CompletionItem
|
|
||||||
var list struct {
|
|
||||||
Items []completionItem `json:"items"`
|
|
||||||
}
|
|
||||||
if json.Unmarshal(result, &list) == nil && len(list.Items) > 0 {
|
|
||||||
for _, item := range list.Items {
|
|
||||||
printItem(item)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var items []completionItem
|
|
||||||
if json.Unmarshal(result, &items) == nil {
|
|
||||||
for _, item := range items {
|
|
||||||
printItem(item)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("(no completions)")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func printItem(item completionItem) {
|
func printItem(item completionItem) {
|
||||||
|
|
|
||||||
|
|
@ -4,91 +4,50 @@ package main
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"ollie/tools/lsp"
|
"ollie/tools/lsp"
|
||||||
|
"ollie/tools/lsp/cmdutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if len(os.Args) > 1 && os.Args[1] == "--bridge" {
|
cmdutil.Run("definition", func(result json.RawMessage) {
|
||||||
if err := lsp.RunBridge(); err != nil {
|
var locations []struct {
|
||||||
fmt.Fprintf(os.Stderr, "bridge: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var args struct {
|
|
||||||
Path string `json:"path"`
|
|
||||||
Line string `json:"line"`
|
|
||||||
Col string `json:"col"`
|
|
||||||
}
|
|
||||||
if err := json.NewDecoder(os.Stdin).Decode(&args); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
file, _ := filepath.Abs(args.Path)
|
|
||||||
line, _ := strconv.Atoi(args.Line)
|
|
||||||
col, _ := strconv.Atoi(args.Col)
|
|
||||||
|
|
||||||
result, err := lsp.ClientRequest(&lsp.BridgeRequest{
|
|
||||||
Method: "definition",
|
|
||||||
File: file,
|
|
||||||
Line: line - 1, // 1-indexed → 0-indexed
|
|
||||||
Col: col - 1, // 1-indexed → 0-indexed
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if result == nil || string(result) == "null" {
|
|
||||||
fmt.Println("(no definition found)")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse location(s)
|
|
||||||
var locations []struct {
|
|
||||||
URI string `json:"uri"`
|
|
||||||
TargetURI string `json:"targetUri"`
|
|
||||||
Range *rang `json:"range"`
|
|
||||||
TargetRange *rang `json:"targetRange"`
|
|
||||||
}
|
|
||||||
// Try as array first
|
|
||||||
if err := json.Unmarshal(result, &locations); err != nil {
|
|
||||||
// Try as single location
|
|
||||||
var single struct {
|
|
||||||
URI string `json:"uri"`
|
URI string `json:"uri"`
|
||||||
TargetURI string `json:"targetUri"`
|
TargetURI string `json:"targetUri"`
|
||||||
Range *rang `json:"range"`
|
Range *rang `json:"range"`
|
||||||
TargetRange *rang `json:"targetRange"`
|
TargetRange *rang `json:"targetRange"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(result, &single); err != nil {
|
if err := json.Unmarshal(result, &locations); err != nil {
|
||||||
fmt.Println("(no definition found)")
|
var single struct {
|
||||||
return
|
URI string `json:"uri"`
|
||||||
|
TargetURI string `json:"targetUri"`
|
||||||
|
Range *rang `json:"range"`
|
||||||
|
TargetRange *rang `json:"targetRange"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(result, &single); err != nil {
|
||||||
|
fmt.Println("(no definition found)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
locations = append(locations, single)
|
||||||
}
|
}
|
||||||
locations = append(locations, single)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, loc := range locations {
|
for _, loc := range locations {
|
||||||
uri := loc.URI
|
uri := loc.URI
|
||||||
if uri == "" {
|
if uri == "" {
|
||||||
uri = loc.TargetURI
|
uri = loc.TargetURI
|
||||||
|
}
|
||||||
|
path := lsp.URIToPath(uri)
|
||||||
|
r := loc.Range
|
||||||
|
if r == nil {
|
||||||
|
r = loc.TargetRange
|
||||||
|
}
|
||||||
|
if r == nil {
|
||||||
|
fmt.Println(path)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Printf("%s:%d:%d\n", path, r.Start.Line+1, r.Start.Character+1)
|
||||||
}
|
}
|
||||||
path := lsp.URIToPath(uri)
|
})
|
||||||
r := loc.Range
|
|
||||||
if r == nil {
|
|
||||||
r = loc.TargetRange
|
|
||||||
}
|
|
||||||
if r == nil {
|
|
||||||
fmt.Println(path)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
fmt.Printf("%s:%d:%d\n", path, r.Start.Line+1, r.Start.Character+1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type rang struct {
|
type rang struct {
|
||||||
|
|
|
||||||
|
|
@ -8,50 +8,48 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"ollie/tools/lsp"
|
"ollie/tools/lsp"
|
||||||
|
"ollie/tools/lsp/cmdutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if len(os.Args) > 1 && os.Args[1] == "--bridge" {
|
cmdutil.RunCustom(func() {
|
||||||
lsp.RunBridge()
|
var args struct {
|
||||||
return
|
Path string `json:"path"`
|
||||||
}
|
}
|
||||||
|
if err := json.NewDecoder(os.Stdin).Decode(&args); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
var args struct {
|
file, _ := filepath.Abs(args.Path)
|
||||||
Path string `json:"path"`
|
|
||||||
}
|
|
||||||
if err := json.NewDecoder(os.Stdin).Decode(&args); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
file, _ := filepath.Abs(args.Path)
|
result, err := lsp.ClientRequest(&lsp.BridgeRequest{
|
||||||
|
Method: "diagnostics",
|
||||||
|
File: file,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
result, err := lsp.ClientRequest(&lsp.BridgeRequest{
|
if result == nil || string(result) == "null" || string(result) == "[]" {
|
||||||
Method: "diagnostics",
|
fmt.Println("(no diagnostics)")
|
||||||
File: file,
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var diags []diagnostic
|
||||||
|
if err := json.Unmarshal(result, &diags); err != nil {
|
||||||
|
fmt.Println("(no diagnostics)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, d := range diags {
|
||||||
|
sev := severity(d.Severity)
|
||||||
|
line := d.Range.Start.Line + 1
|
||||||
|
col := d.Range.Start.Character + 1
|
||||||
|
fmt.Printf("%s:%d:%d: %s: %s\n", file, line, col, sev, d.Message)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if result == nil || string(result) == "null" || string(result) == "[]" {
|
|
||||||
fmt.Println("(no diagnostics)")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var diags []diagnostic
|
|
||||||
if err := json.Unmarshal(result, &diags); err != nil {
|
|
||||||
fmt.Println("(no diagnostics)")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, d := range diags {
|
|
||||||
sev := severity(d.Severity)
|
|
||||||
line := d.Range.Start.Line + 1
|
|
||||||
col := d.Range.Start.Character + 1
|
|
||||||
fmt.Printf("%s:%d:%d: %s: %s\n", file, line, col, sev, d.Message)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type diagnostic struct {
|
type diagnostic struct {
|
||||||
|
|
|
||||||
|
|
@ -4,66 +4,29 @@ package main
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"ollie/tools/lsp"
|
"ollie/tools/lsp/cmdutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if len(os.Args) > 1 && os.Args[1] == "--bridge" {
|
cmdutil.Run("hover", func(result json.RawMessage) {
|
||||||
lsp.RunBridge()
|
var hover struct {
|
||||||
return
|
Contents struct {
|
||||||
}
|
Kind string `json:"kind"`
|
||||||
|
Value string `json:"value"`
|
||||||
var args struct {
|
} `json:"contents"`
|
||||||
Path string `json:"path"`
|
|
||||||
Line string `json:"line"`
|
|
||||||
Col string `json:"col"`
|
|
||||||
}
|
|
||||||
if err := json.NewDecoder(os.Stdin).Decode(&args); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
file, _ := filepath.Abs(args.Path)
|
|
||||||
line, _ := strconv.Atoi(args.Line)
|
|
||||||
col, _ := strconv.Atoi(args.Col)
|
|
||||||
|
|
||||||
result, err := lsp.ClientRequest(&lsp.BridgeRequest{
|
|
||||||
Method: "hover",
|
|
||||||
File: file,
|
|
||||||
Line: line - 1,
|
|
||||||
Col: col - 1,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if result == nil || string(result) == "null" {
|
|
||||||
fmt.Println("(no hover information)")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var hover struct {
|
|
||||||
Contents struct {
|
|
||||||
Kind string `json:"kind"`
|
|
||||||
Value string `json:"value"`
|
|
||||||
} `json:"contents"`
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(result, &hover); err != nil {
|
|
||||||
// Try as plain string
|
|
||||||
var plain struct {
|
|
||||||
Contents string `json:"contents"`
|
|
||||||
}
|
}
|
||||||
if json.Unmarshal(result, &plain) == nil {
|
if err := json.Unmarshal(result, &hover); err != nil {
|
||||||
fmt.Println(plain.Contents)
|
var plain struct {
|
||||||
|
Contents string `json:"contents"`
|
||||||
|
}
|
||||||
|
if json.Unmarshal(result, &plain) == nil {
|
||||||
|
fmt.Println(plain.Contents)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println("(no hover information)")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Println("(no hover information)")
|
fmt.Println(hover.Contents.Value)
|
||||||
return
|
})
|
||||||
}
|
|
||||||
fmt.Println(hover.Contents.Value)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,66 +4,31 @@ package main
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"ollie/tools/lsp"
|
"ollie/tools/lsp"
|
||||||
|
"ollie/tools/lsp/cmdutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if len(os.Args) > 1 && os.Args[1] == "--bridge" {
|
cmdutil.Run("references", func(result json.RawMessage) {
|
||||||
lsp.RunBridge()
|
var locations []struct {
|
||||||
return
|
URI string `json:"uri"`
|
||||||
}
|
Range struct {
|
||||||
|
Start struct {
|
||||||
|
Line int `json:"line"`
|
||||||
|
Character int `json:"character"`
|
||||||
|
} `json:"start"`
|
||||||
|
} `json:"range"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(result, &locations); err != nil {
|
||||||
|
fmt.Println("(no references found)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var args struct {
|
fmt.Printf("STATUS=ok MATCH_COUNT=%d\n", len(locations))
|
||||||
Path string `json:"path"`
|
for _, loc := range locations {
|
||||||
Line string `json:"line"`
|
path := lsp.URIToPath(loc.URI)
|
||||||
Col string `json:"col"`
|
fmt.Printf("%s:%d:%d\n", path, loc.Range.Start.Line+1, loc.Range.Start.Character+1)
|
||||||
}
|
}
|
||||||
if err := json.NewDecoder(os.Stdin).Decode(&args); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
file, _ := filepath.Abs(args.Path)
|
|
||||||
line, _ := strconv.Atoi(args.Line)
|
|
||||||
col, _ := strconv.Atoi(args.Col)
|
|
||||||
|
|
||||||
result, err := lsp.ClientRequest(&lsp.BridgeRequest{
|
|
||||||
Method: "references",
|
|
||||||
File: file,
|
|
||||||
Line: line - 1,
|
|
||||||
Col: col - 1,
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if result == nil || string(result) == "null" {
|
|
||||||
fmt.Println("(no references found)")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var locations []struct {
|
|
||||||
URI string `json:"uri"`
|
|
||||||
Range struct {
|
|
||||||
Start struct {
|
|
||||||
Line int `json:"line"`
|
|
||||||
Character int `json:"character"`
|
|
||||||
} `json:"start"`
|
|
||||||
} `json:"range"`
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(result, &locations); err != nil {
|
|
||||||
fmt.Println("(no references found)")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("STATUS=ok MATCH_COUNT=%d\n", len(locations))
|
|
||||||
for _, loc := range locations {
|
|
||||||
path := lsp.URIToPath(loc.URI)
|
|
||||||
fmt.Printf("%s:%d:%d\n", path, loc.Range.Start.Line+1, loc.Range.Start.Character+1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,53 +11,50 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"ollie/tools/lsp"
|
"ollie/tools/lsp"
|
||||||
|
"ollie/tools/lsp/cmdutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if len(os.Args) > 1 && os.Args[1] == "--bridge" {
|
cmdutil.RunCustom(func() {
|
||||||
lsp.RunBridge()
|
var args struct {
|
||||||
return
|
Path string `json:"path"`
|
||||||
}
|
Line string `json:"line"`
|
||||||
|
Col string `json:"col"`
|
||||||
|
NewName string `json:"new_name"`
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(os.Stdin).Decode(&args); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
var args struct {
|
file, _ := filepath.Abs(args.Path)
|
||||||
Path string `json:"path"`
|
line, _ := strconv.Atoi(args.Line)
|
||||||
Line string `json:"line"`
|
col, _ := strconv.Atoi(args.Col)
|
||||||
Col string `json:"col"`
|
|
||||||
NewName string `json:"new_name"`
|
|
||||||
}
|
|
||||||
if err := json.NewDecoder(os.Stdin).Decode(&args); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
file, _ := filepath.Abs(args.Path)
|
result, err := lsp.ClientRequest(&lsp.BridgeRequest{
|
||||||
line, _ := strconv.Atoi(args.Line)
|
Method: "rename",
|
||||||
col, _ := strconv.Atoi(args.Col)
|
File: file,
|
||||||
|
Line: line - 1,
|
||||||
|
Col: col - 1,
|
||||||
|
NewName: args.NewName,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
result, err := lsp.ClientRequest(&lsp.BridgeRequest{
|
if result == nil || string(result) == "null" {
|
||||||
Method: "rename",
|
fmt.Println("(rename produced no changes)")
|
||||||
File: file,
|
return
|
||||||
Line: line - 1,
|
}
|
||||||
Col: col - 1,
|
|
||||||
NewName: args.NewName,
|
summary := applyWorkspaceEdit(result)
|
||||||
|
if summary == "" {
|
||||||
|
fmt.Println("(rename produced no changes)")
|
||||||
|
} else {
|
||||||
|
fmt.Print(summary)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if result == nil || string(result) == "null" {
|
|
||||||
fmt.Println("(rename produced no changes)")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse WorkspaceEdit and apply
|
|
||||||
summary := applyWorkspaceEdit(result)
|
|
||||||
if summary == "" {
|
|
||||||
fmt.Println("(rename produced no changes)")
|
|
||||||
} else {
|
|
||||||
fmt.Print(summary)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyWorkspaceEdit(raw json.RawMessage) string {
|
func applyWorkspaceEdit(raw json.RawMessage) string {
|
||||||
|
|
@ -74,7 +71,6 @@ func applyWorkspaceEdit(raw json.RawMessage) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalize to path → edits map
|
|
||||||
changes := make(map[string][]textEdit)
|
changes := make(map[string][]textEdit)
|
||||||
if len(edit.DocumentChanges) > 0 {
|
if len(edit.DocumentChanges) > 0 {
|
||||||
for _, dc := range edit.DocumentChanges {
|
for _, dc := range edit.DocumentChanges {
|
||||||
|
|
@ -97,7 +93,6 @@ func applyWorkspaceEdit(raw json.RawMessage) string {
|
||||||
}
|
}
|
||||||
lines := strings.SplitAfter(string(data), "\n")
|
lines := strings.SplitAfter(string(data), "\n")
|
||||||
|
|
||||||
// Sort edits reverse (bottom-up) to preserve positions
|
|
||||||
sort.Slice(edits, func(i, j int) bool {
|
sort.Slice(edits, func(i, j int) bool {
|
||||||
if edits[i].Range.Start.Line != edits[j].Range.Start.Line {
|
if edits[i].Range.Start.Line != edits[j].Range.Start.Line {
|
||||||
return edits[i].Range.Start.Line > edits[j].Range.Start.Line
|
return edits[i].Range.Start.Line > edits[j].Range.Start.Line
|
||||||
|
|
|
||||||
|
|
@ -8,61 +8,57 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"ollie/tools/lsp"
|
"ollie/tools/lsp"
|
||||||
|
"ollie/tools/lsp/cmdutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if len(os.Args) > 1 && os.Args[1] == "--bridge" {
|
cmdutil.RunCustom(func() {
|
||||||
lsp.RunBridge()
|
var args struct {
|
||||||
return
|
Path string `json:"path"`
|
||||||
}
|
Query string `json:"query"`
|
||||||
|
}
|
||||||
var args struct {
|
if err := json.NewDecoder(os.Stdin).Decode(&args); err != nil {
|
||||||
Path string `json:"path"`
|
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||||
Query string `json:"query"`
|
os.Exit(1)
|
||||||
}
|
|
||||||
if err := json.NewDecoder(os.Stdin).Decode(&args); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
file, _ := filepath.Abs(args.Path)
|
|
||||||
workspace := args.Query != ""
|
|
||||||
|
|
||||||
result, err := lsp.ClientRequest(&lsp.BridgeRequest{
|
|
||||||
Method: "symbols",
|
|
||||||
File: file,
|
|
||||||
Query: args.Query,
|
|
||||||
Workspace: workspace,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if result == nil || string(result) == "null" {
|
|
||||||
fmt.Println("(no symbols found)")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try document symbols (hierarchical)
|
|
||||||
var docSymbols []documentSymbol
|
|
||||||
if json.Unmarshal(result, &docSymbols) == nil && len(docSymbols) > 0 {
|
|
||||||
printDocSymbols(docSymbols, 0)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try workspace symbols (flat)
|
|
||||||
var wsSymbols []workspaceSymbol
|
|
||||||
if json.Unmarshal(result, &wsSymbols) == nil && len(wsSymbols) > 0 {
|
|
||||||
for _, s := range wsSymbols {
|
|
||||||
path := lsp.URIToPath(s.Location.URI)
|
|
||||||
line := s.Location.Range.Start.Line + 1
|
|
||||||
fmt.Printf("%s:%d %s [%s]\n", path, line, s.Name, symbolKind(s.Kind))
|
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("(no symbols found)")
|
file, _ := filepath.Abs(args.Path)
|
||||||
|
workspace := args.Query != ""
|
||||||
|
|
||||||
|
result, err := lsp.ClientRequest(&lsp.BridgeRequest{
|
||||||
|
Method: "symbols",
|
||||||
|
File: file,
|
||||||
|
Query: args.Query,
|
||||||
|
Workspace: workspace,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result == nil || string(result) == "null" {
|
||||||
|
fmt.Println("(no symbols found)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var docSymbols []documentSymbol
|
||||||
|
if json.Unmarshal(result, &docSymbols) == nil && len(docSymbols) > 0 {
|
||||||
|
printDocSymbols(docSymbols, 0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var wsSymbols []workspaceSymbol
|
||||||
|
if json.Unmarshal(result, &wsSymbols) == nil && len(wsSymbols) > 0 {
|
||||||
|
for _, s := range wsSymbols {
|
||||||
|
path := lsp.URIToPath(s.Location.URI)
|
||||||
|
line := s.Location.Range.Start.Line + 1
|
||||||
|
fmt.Printf("%s:%d %s [%s]\n", path, line, s.Name, symbolKind(s.Kind))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("(no symbols found)")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func printDocSymbols(symbols []documentSymbol, indent int) {
|
func printDocSymbols(symbols []documentSymbol, indent int) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
// Package cmdutil provides shared boilerplate for LSP tool command binaries.
|
||||||
|
package cmdutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"ollie/tools/lsp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PositionArgs are the common args for position-based LSP commands.
|
||||||
|
type PositionArgs struct {
|
||||||
|
Path string `json:"path"`
|
||||||
|
Line string `json:"line"`
|
||||||
|
Col string `json:"col"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolvePosition converts the string fields to absolute path and 0-indexed ints.
|
||||||
|
func (a PositionArgs) ResolvePosition() (file string, line, col int) {
|
||||||
|
file, _ = filepath.Abs(a.Path)
|
||||||
|
l, _ := strconv.Atoi(a.Line)
|
||||||
|
c, _ := strconv.Atoi(a.Col)
|
||||||
|
return file, l - 1, c - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run is the common entry point for position-based LSP commands.
|
||||||
|
// It handles --bridge, reads PositionArgs from stdin, dispatches the request,
|
||||||
|
// and passes the result to format. If format is nil, raw JSON is printed.
|
||||||
|
func Run(method string, format func(json.RawMessage)) {
|
||||||
|
if len(os.Args) > 1 && os.Args[1] == "--bridge" {
|
||||||
|
if err := lsp.RunBridge(); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "bridge: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var args PositionArgs
|
||||||
|
if err := json.NewDecoder(os.Stdin).Decode(&args); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
file, line, col := args.ResolvePosition()
|
||||||
|
|
||||||
|
result, err := lsp.ClientRequest(&lsp.BridgeRequest{
|
||||||
|
Method: method,
|
||||||
|
File: file,
|
||||||
|
Line: line,
|
||||||
|
Col: col,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result == nil || string(result) == "null" {
|
||||||
|
fmt.Printf("(no %s found)\n", method)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if format != nil {
|
||||||
|
format(result)
|
||||||
|
} else {
|
||||||
|
os.Stdout.Write(result)
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunCustom handles --bridge and then calls fn for custom argument handling.
|
||||||
|
// Use for commands that don't follow the simple position-args pattern
|
||||||
|
// (e.g. diagnostics, rename, symbols).
|
||||||
|
func RunCustom(fn func()) {
|
||||||
|
if len(os.Args) > 1 && os.Args[1] == "--bridge" {
|
||||||
|
if err := lsp.RunBridge(); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "bridge: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fn()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
package toolsrv
|
||||||
|
|
||||||
|
import "encoding/json"
|
||||||
|
|
||||||
|
// RPCRequest is a JSON-RPC 2.0 request.
|
||||||
|
type RPCRequest struct {
|
||||||
|
JSONRPC string `json:"jsonrpc"`
|
||||||
|
ID json.RawMessage `json:"id"`
|
||||||
|
Method string `json:"method"`
|
||||||
|
Params json.RawMessage `json:"params,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RPCError is a JSON-RPC 2.0 error object.
|
||||||
|
type RPCError struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// OutputNotification carries streamed tool output data.
|
||||||
|
type OutputNotification struct {
|
||||||
|
Data string `json:"data"`
|
||||||
|
}
|
||||||
|
|
@ -46,7 +46,7 @@ func localDial(ctx context.Context, cwd string, opts ...Option) (*transport, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate a unique socket path.
|
// Generate a unique socket path.
|
||||||
sockDir := filepath.Join(runtimeDir(), "ollie")
|
sockDir := filepath.Join(paths.RuntimeDir(), "ollie")
|
||||||
os.MkdirAll(sockDir, 0700)
|
os.MkdirAll(sockDir, 0700)
|
||||||
sockPath := filepath.Join(sockDir, fmt.Sprintf("toolsrv-%d-%d.sock", os.Getpid(), socketSeq.Add(1)))
|
sockPath := filepath.Join(sockDir, fmt.Sprintf("toolsrv-%d-%d.sock", os.Getpid(), socketSeq.Add(1)))
|
||||||
|
|
||||||
|
|
@ -137,7 +137,7 @@ func sshDial(ctx context.Context, cfg RemoteConfig) (*transport, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate socket paths.
|
// Generate socket paths.
|
||||||
sockDir := filepath.Join(runtimeDir(), "ollie")
|
sockDir := filepath.Join(paths.RuntimeDir(), "ollie")
|
||||||
os.MkdirAll(sockDir, 0700)
|
os.MkdirAll(sockDir, 0700)
|
||||||
localSock := filepath.Join(sockDir, fmt.Sprintf("remote-%s-%d-%d.sock", cfg.SSHTarget, os.Getpid(), socketSeq.Add(1)))
|
localSock := filepath.Join(sockDir, fmt.Sprintf("remote-%s-%d-%d.sock", cfg.SSHTarget, os.Getpid(), socketSeq.Add(1)))
|
||||||
remoteSock := fmt.Sprintf("/tmp/ollie-toolsrv-%d.sock", os.Getpid())
|
remoteSock := fmt.Sprintf("/tmp/ollie-toolsrv-%d.sock", os.Getpid())
|
||||||
|
|
@ -285,11 +285,3 @@ func shellEscape(s string) string {
|
||||||
}
|
}
|
||||||
return "'" + strings.ReplaceAll(s, "'", "'\\''") + "'"
|
return "'" + strings.ReplaceAll(s, "'", "'\\''") + "'"
|
||||||
}
|
}
|
||||||
|
|
||||||
// runtimeDir returns XDG_RUNTIME_DIR or a fallback.
|
|
||||||
func runtimeDir() string {
|
|
||||||
if d := os.Getenv("XDG_RUNTIME_DIR"); d != "" {
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
return filepath.Join(os.TempDir(), fmt.Sprintf("runtime-%d", os.Getuid()))
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue