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"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
|
@ -240,3 +241,22 @@ func isContextOverflow(body string) bool {
|
|||
}
|
||||
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
|
||||
|
||||
// CodeWhispererBackend implements Backend for Amazon CodeWhisperer / Kiro.
|
||||
// KiroBackend implements Backend for the Kiro streaming API.
|
||||
//
|
||||
// COVERAGE: Intentionally untested. Reverse-engineered Kiro streaming API
|
||||
// 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
|
||||
// ($XDG_DATA_HOME/kiro-cli/data.sqlite3 on Linux).
|
||||
// - "sqlite:///path/to/data.sqlite3" → read from the specified SQLite file.
|
||||
|
|
@ -35,7 +35,7 @@ import (
|
|||
|
||||
// ── Public constructor ────────────────────────────────────────────────────────
|
||||
|
||||
type CodeWhispererBackend struct {
|
||||
type KiroBackend struct {
|
||||
baseBackend
|
||||
endpoint string // overrides auth-derived endpoint if non-empty
|
||||
extraHeaders map[string]string
|
||||
|
|
@ -49,13 +49,13 @@ type CodeWhispererBackend struct {
|
|||
modelsCacheAt time.Time
|
||||
}
|
||||
|
||||
// NewCodeWhisperer returns a CodeWhisperer backend. See package doc for apiKey semantics.
|
||||
func NewCodeWhisperer(apiKey string) (*CodeWhispererBackend, error) {
|
||||
// NewKiro returns a Kiro backend. See package doc for apiKey semantics.
|
||||
func NewKiro(apiKey string) (*KiroBackend, error) {
|
||||
if strings.TrimSpace(apiKey) == "" {
|
||||
apiKey = kiroDefaultSQLiteKey()
|
||||
}
|
||||
authSource, err := newKiroAuthSource(apiKey)
|
||||
b := &CodeWhispererBackend{
|
||||
b := &KiroBackend{
|
||||
baseBackend: baseBackend{name: "kiro"},
|
||||
authSource: authSource,
|
||||
authInitErr: err,
|
||||
|
|
@ -65,9 +65,9 @@ func NewCodeWhisperer(apiKey string) (*CodeWhispererBackend, error) {
|
|||
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()
|
||||
if b.modelsCache != nil && time.Since(b.modelsCacheAt) < 24*time.Hour {
|
||||
result := b.modelsCache
|
||||
|
|
@ -92,7 +92,7 @@ func (b *CodeWhispererBackend) Models(ctx context.Context) []string {
|
|||
return ids
|
||||
}
|
||||
|
||||
func (b *CodeWhispererBackend) fetchModels(ctx context.Context) *kiroListModelsResponse {
|
||||
func (b *KiroBackend) fetchModels(ctx context.Context) *kiroListModelsResponse {
|
||||
if b.authInitErr != nil {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -113,7 +113,7 @@ func (b *CodeWhispererBackend) fetchModels(ctx context.Context) *kiroListModelsR
|
|||
return resp
|
||||
}
|
||||
|
||||
func (b *CodeWhispererBackend) ContextLength(ctx context.Context) int {
|
||||
func (b *KiroBackend) ContextLength(ctx context.Context) int {
|
||||
if b.ctxLen > 0 {
|
||||
return b.ctxLen
|
||||
}
|
||||
|
|
@ -136,7 +136,7 @@ func (b *CodeWhispererBackend) ContextLength(ctx context.Context) int {
|
|||
|
||||
// ── Backend interface ─────────────────────────────────────────────────────────
|
||||
|
||||
func (b *CodeWhispererBackend) ChatStream(
|
||||
func (b *KiroBackend) ChatStream(
|
||||
ctx context.Context,
|
||||
messages []Message,
|
||||
tools []Tool,
|
||||
|
|
@ -172,7 +172,7 @@ const kiroMaxThrottleRetries = 5
|
|||
// Tests can override this.
|
||||
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
|
||||
for {
|
||||
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 != "" {
|
||||
return ep, nil
|
||||
}
|
||||
return b.authSource.DefaultEndpoint(ctx)
|
||||
}
|
||||
|
||||
func (b *CodeWhispererBackend) shouldRefresh(err error) bool {
|
||||
func (b *KiroBackend) shouldRefresh(err error) bool {
|
||||
if !b.authSource.CanRefresh() {
|
||||
return false
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
package backend
|
||||
|
||||
// Low-level plumbing for the Amazon CodeWhisperer / Kiro backend.
|
||||
// Low-level plumbing for the Kiro backend.
|
||||
// COVERAGE: Intentionally untested. See codewhisperer.go.
|
||||
//
|
||||
// - Wire types for the GenerateAssistantResponse API
|
||||
|
|
@ -95,7 +95,7 @@ func newBackendFromConfig(which string, cfg configFile) (Backend, error) {
|
|||
if token == "" {
|
||||
token = os.Getenv("OLLIE_KIRO_TOKEN")
|
||||
}
|
||||
b, err = NewCodeWhisperer(token)
|
||||
b, err = NewKiro(token)
|
||||
|
||||
case "gemini":
|
||||
key := bc.Key
|
||||
|
|
|
|||
|
|
@ -9,9 +9,7 @@ import (
|
|||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
type rpcRequest struct {
|
||||
JSONRPC string `json:"jsonrpc"`
|
||||
ID json.RawMessage `json:"id"`
|
||||
Method string `json:"method"`
|
||||
Params json.RawMessage `json:"params"`
|
||||
}
|
||||
|
||||
type rpcResponse struct {
|
||||
JSONRPC string `json:"jsonrpc"`
|
||||
ID json.RawMessage `json:"id"`
|
||||
Result any `json:"result,omitempty"`
|
||||
Error *rpcError `json:"error,omitempty"`
|
||||
Error *toolsrv.RPCError `json:"error,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 {
|
||||
mu sync.Mutex
|
||||
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)}
|
||||
|
||||
for {
|
||||
var req rpcRequest
|
||||
var req toolsrv.RPCRequest
|
||||
if err := dec.Decode(&req); err != nil {
|
||||
if err == io.EOF || ctx.Err() != nil {
|
||||
return
|
||||
|
|
@ -206,7 +190,7 @@ func serveRPC(ctx context.Context, srv *toolsrv.Server, in io.Reader, out io.Wri
|
|||
enc.Encode(rpcResponse{
|
||||
JSONRPC: "2.0",
|
||||
ID: req.ID,
|
||||
Error: &rpcError{Code: -32700, Message: "parse error: " + err.Error()},
|
||||
Error: &toolsrv.RPCError{Code: -32700, Message: "parse error: " + err.Error()},
|
||||
})
|
||||
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 {
|
||||
case "list_tools":
|
||||
tools, err := srv.ListTools()
|
||||
|
|
@ -223,7 +207,7 @@ func handleRPC(ctx context.Context, srv *toolsrv.Server, req rpcRequest, enc *lo
|
|||
enc.Encode(rpcResponse{
|
||||
JSONRPC: "2.0",
|
||||
ID: req.ID,
|
||||
Error: &rpcError{Code: -32000, Message: err.Error()},
|
||||
Error: &toolsrv.RPCError{Code: -32000, Message: err.Error()},
|
||||
})
|
||||
} else {
|
||||
enc.Encode(rpcResponse{
|
||||
|
|
@ -258,7 +242,7 @@ func handleRPC(ctx context.Context, srv *toolsrv.Server, req rpcRequest, enc *lo
|
|||
Value string `json:"value"`
|
||||
}
|
||||
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
|
||||
}
|
||||
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"`
|
||||
}
|
||||
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
|
||||
}
|
||||
srv.SetCWD(params.Dir)
|
||||
|
|
@ -280,7 +264,7 @@ func handleRPC(ctx context.Context, srv *toolsrv.Server, req rpcRequest, enc *lo
|
|||
Names []string `json:"names"`
|
||||
}
|
||||
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
|
||||
}
|
||||
srv.SetAllowTools(params.Names)
|
||||
|
|
@ -299,11 +283,11 @@ func handleRPC(ctx context.Context, srv *toolsrv.Server, req rpcRequest, enc *lo
|
|||
Signal int `json:"signal"`
|
||||
}
|
||||
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
|
||||
}
|
||||
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 {
|
||||
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"`
|
||||
}
|
||||
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
|
||||
}
|
||||
out, err := srv.GetDetachedOutput(params.PID)
|
||||
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 {
|
||||
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"`
|
||||
}
|
||||
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
|
||||
}
|
||||
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"`
|
||||
}
|
||||
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
|
||||
}
|
||||
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 {
|
||||
res, _ := json.Marshal(map[string]string{"result": "loaded: " + params.Name})
|
||||
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{
|
||||
JSONRPC: "2.0",
|
||||
ID: req.ID,
|
||||
Result: outputNotif{Data: data},
|
||||
Result: toolsrv.OutputNotification{Data: data},
|
||||
Stream: true,
|
||||
})
|
||||
})
|
||||
|
|
@ -363,7 +347,7 @@ func handleRPC(ctx context.Context, srv *toolsrv.Server, req rpcRequest, enc *lo
|
|||
enc.Encode(rpcResponse{
|
||||
JSONRPC: "2.0",
|
||||
ID: req.ID,
|
||||
Error: &rpcError{Code: -32000, Message: err.Error()},
|
||||
Error: &toolsrv.RPCError{Code: -32000, Message: err.Error()},
|
||||
})
|
||||
} else {
|
||||
enc.Encode(rpcResponse{
|
||||
|
|
|
|||
|
|
@ -84,11 +84,7 @@ func runServer(sockPath string) {
|
|||
// Server creation deferred until after bypass broker is ready (see below)
|
||||
|
||||
// Start bypass broker
|
||||
xdg := os.Getenv("XDG_RUNTIME_DIR")
|
||||
if xdg == "" {
|
||||
xdg = fmt.Sprintf("/run/user/%d", os.Getuid())
|
||||
}
|
||||
bypassSocket := filepath.Join(xdg, "ollie", "bypass.sock")
|
||||
bypassSocket := filepath.Join(paths.RuntimeDir(), "ollie", "bypass.sock")
|
||||
os.Setenv("OLLIE_BYPASS_SOCKET", bypassSocket) //nolint:errcheck
|
||||
policyPath := filepath.Join(paths.DataDir(), "bypass-policy.yaml")
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package paths
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
|
@ -16,6 +17,14 @@ func ExpandHome(path string) string {
|
|||
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.
|
||||
func xdgConfigHome() string {
|
||||
if p := os.Getenv("XDG_CONFIG_HOME"); p != "" {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
package lsp
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"ollie/paths"
|
||||
)
|
||||
|
||||
// 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.
|
||||
func SockPath() string {
|
||||
xdg := os.Getenv("XDG_RUNTIME_DIR")
|
||||
if xdg == "" {
|
||||
xdg = "/tmp"
|
||||
}
|
||||
return filepath.Join(xdg, "ollie-lsp.sock")
|
||||
return filepath.Join(paths.RuntimeDir(), "ollie-lsp.sock")
|
||||
}
|
||||
|
||||
// PidPath returns the path to the bridge PID file.
|
||||
|
|
|
|||
|
|
@ -4,69 +4,32 @@ package main
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"ollie/tools/lsp"
|
||||
"ollie/tools/lsp/cmdutil"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) > 1 && os.Args[1] == "--bridge" {
|
||||
lsp.RunBridge()
|
||||
return
|
||||
}
|
||||
cmdutil.Run("completion", func(result json.RawMessage) {
|
||||
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 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)
|
||||
}
|
||||
var items []completionItem
|
||||
if json.Unmarshal(result, &items) == nil {
|
||||
for _, item := range items {
|
||||
printItem(item)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
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)")
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -4,91 +4,50 @@ package main
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"ollie/tools/lsp"
|
||||
"ollie/tools/lsp/cmdutil"
|
||||
)
|
||||
|
||||
func main() {
|
||||
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 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 {
|
||||
cmdutil.Run("definition", func(result json.RawMessage) {
|
||||
var locations []struct {
|
||||
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
|
||||
if err := json.Unmarshal(result, &locations); err != nil {
|
||||
var single struct {
|
||||
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 {
|
||||
uri := loc.URI
|
||||
if uri == "" {
|
||||
uri = loc.TargetURI
|
||||
for _, loc := range locations {
|
||||
uri := loc.URI
|
||||
if uri == "" {
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -8,50 +8,48 @@ import (
|
|||
"path/filepath"
|
||||
|
||||
"ollie/tools/lsp"
|
||||
"ollie/tools/lsp/cmdutil"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) > 1 && os.Args[1] == "--bridge" {
|
||||
lsp.RunBridge()
|
||||
return
|
||||
}
|
||||
cmdutil.RunCustom(func() {
|
||||
var args struct {
|
||||
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 {
|
||||
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)
|
||||
|
||||
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{
|
||||
Method: "diagnostics",
|
||||
File: file,
|
||||
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)
|
||||
}
|
||||
})
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -4,66 +4,29 @@ package main
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"ollie/tools/lsp"
|
||||
"ollie/tools/lsp/cmdutil"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) > 1 && os.Args[1] == "--bridge" {
|
||||
lsp.RunBridge()
|
||||
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: "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"`
|
||||
cmdutil.Run("hover", func(result json.RawMessage) {
|
||||
var hover struct {
|
||||
Contents struct {
|
||||
Kind string `json:"kind"`
|
||||
Value string `json:"value"`
|
||||
} `json:"contents"`
|
||||
}
|
||||
if json.Unmarshal(result, &plain) == nil {
|
||||
fmt.Println(plain.Contents)
|
||||
if err := json.Unmarshal(result, &hover); err != nil {
|
||||
var plain struct {
|
||||
Contents string `json:"contents"`
|
||||
}
|
||||
if json.Unmarshal(result, &plain) == nil {
|
||||
fmt.Println(plain.Contents)
|
||||
return
|
||||
}
|
||||
fmt.Println("(no hover information)")
|
||||
return
|
||||
}
|
||||
fmt.Println("(no hover information)")
|
||||
return
|
||||
}
|
||||
fmt.Println(hover.Contents.Value)
|
||||
fmt.Println(hover.Contents.Value)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,66 +4,31 @@ package main
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"ollie/tools/lsp"
|
||||
"ollie/tools/lsp/cmdutil"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) > 1 && os.Args[1] == "--bridge" {
|
||||
lsp.RunBridge()
|
||||
return
|
||||
}
|
||||
cmdutil.Run("references", func(result json.RawMessage) {
|
||||
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
|
||||
}
|
||||
|
||||
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: "references",
|
||||
File: file,
|
||||
Line: line - 1,
|
||||
Col: col - 1,
|
||||
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)
|
||||
}
|
||||
})
|
||||
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"
|
||||
|
||||
"ollie/tools/lsp"
|
||||
"ollie/tools/lsp/cmdutil"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) > 1 && os.Args[1] == "--bridge" {
|
||||
lsp.RunBridge()
|
||||
return
|
||||
}
|
||||
cmdutil.RunCustom(func() {
|
||||
var args struct {
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
file, _ := filepath.Abs(args.Path)
|
||||
line, _ := strconv.Atoi(args.Line)
|
||||
col, _ := strconv.Atoi(args.Col)
|
||||
|
||||
file, _ := filepath.Abs(args.Path)
|
||||
line, _ := strconv.Atoi(args.Line)
|
||||
col, _ := strconv.Atoi(args.Col)
|
||||
result, err := lsp.ClientRequest(&lsp.BridgeRequest{
|
||||
Method: "rename",
|
||||
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{
|
||||
Method: "rename",
|
||||
File: file,
|
||||
Line: line - 1,
|
||||
Col: col - 1,
|
||||
NewName: args.NewName,
|
||||
if result == nil || string(result) == "null" {
|
||||
fmt.Println("(rename produced no changes)")
|
||||
return
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
@ -74,7 +71,6 @@ func applyWorkspaceEdit(raw json.RawMessage) string {
|
|||
return ""
|
||||
}
|
||||
|
||||
// Normalize to path → edits map
|
||||
changes := make(map[string][]textEdit)
|
||||
if len(edit.DocumentChanges) > 0 {
|
||||
for _, dc := range edit.DocumentChanges {
|
||||
|
|
@ -97,7 +93,6 @@ func applyWorkspaceEdit(raw json.RawMessage) string {
|
|||
}
|
||||
lines := strings.SplitAfter(string(data), "\n")
|
||||
|
||||
// Sort edits reverse (bottom-up) to preserve positions
|
||||
sort.Slice(edits, func(i, j int) bool {
|
||||
if 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"
|
||||
|
||||
"ollie/tools/lsp"
|
||||
"ollie/tools/lsp/cmdutil"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) > 1 && os.Args[1] == "--bridge" {
|
||||
lsp.RunBridge()
|
||||
return
|
||||
}
|
||||
|
||||
var args struct {
|
||||
Path string `json:"path"`
|
||||
Query string `json:"query"`
|
||||
}
|
||||
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))
|
||||
cmdutil.RunCustom(func() {
|
||||
var args struct {
|
||||
Path string `json:"path"`
|
||||
Query string `json:"query"`
|
||||
}
|
||||
if err := json.NewDecoder(os.Stdin).Decode(&args); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
sockDir := filepath.Join(runtimeDir(), "ollie")
|
||||
sockDir := filepath.Join(paths.RuntimeDir(), "ollie")
|
||||
os.MkdirAll(sockDir, 0700)
|
||||
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.
|
||||
sockDir := filepath.Join(runtimeDir(), "ollie")
|
||||
sockDir := filepath.Join(paths.RuntimeDir(), "ollie")
|
||||
os.MkdirAll(sockDir, 0700)
|
||||
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())
|
||||
|
|
@ -285,11 +285,3 @@ func shellEscape(s string) string {
|
|||
}
|
||||
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