/
main.go
137 lines (122 loc) · 3.39 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package lspcmd
import (
"context"
"fmt"
"io"
"net/http"
"os"
"os/signal"
"github.com/a-h/protocol"
"github.com/a-h/templ/cmd/templ/lspcmd/httpdebug"
"github.com/a-h/templ/cmd/templ/lspcmd/pls"
"github.com/a-h/templ/cmd/templ/lspcmd/proxy"
"go.lsp.dev/jsonrpc2"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
_ "net/http/pprof"
)
type Arguments struct {
Log string
GoplsLog string
GoplsRPCTrace bool
// PPROF sets whether to start a profiling server on localhost:9999
PPROF bool
// HTTPDebug sets the HTTP endpoint to listen on. Leave empty for no web debug.
HTTPDebug string
}
func Run(w io.Writer, args Arguments) (err error) {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, os.Interrupt)
defer func() {
signal.Stop(signalChan)
cancel()
}()
if args.PPROF {
go func() {
_ = http.ListenAndServe("localhost:9999", nil)
}()
}
go func() {
select {
case <-signalChan: // First signal, cancel context.
cancel()
case <-ctx.Done():
}
<-signalChan // Second signal, hard exit.
os.Exit(2)
}()
log := zap.NewNop()
if args.Log != "" {
cfg := zap.NewProductionConfig()
cfg.EncoderConfig.EncodeTime = zapcore.RFC3339TimeEncoder
cfg.OutputPaths = []string{
args.Log,
}
log, err = cfg.Build()
if err != nil {
_, _ = fmt.Fprintf(w, "failed to create logger: %v\n", err)
os.Exit(1)
}
}
defer func() {
_ = log.Sync()
}()
templStream := jsonrpc2.NewStream(newStdRwc(log, "templStream", w, os.Stdin))
return run(ctx, log, templStream, args)
}
func run(ctx context.Context, log *zap.Logger, templStream jsonrpc2.Stream, args Arguments) (err error) {
log.Info("lsp: starting up...")
defer func() {
if r := recover(); r != nil {
log.Fatal("handled panic", zap.Any("recovered", r))
}
}()
log.Info("lsp: starting gopls...")
rwc, err := pls.NewGopls(ctx, log, pls.Options{
Log: args.GoplsLog,
RPCTrace: args.GoplsRPCTrace,
})
if err != nil {
log.Error("failed to start gopls", zap.Error(err))
os.Exit(1)
}
cache := proxy.NewSourceMapCache()
diagnosticCache := proxy.NewDiagnosticCache()
log.Info("creating gopls client")
clientProxy, clientInit := proxy.NewClient(log, cache, diagnosticCache)
_, goplsConn, goplsServer := protocol.NewClient(context.Background(), clientProxy, jsonrpc2.NewStream(rwc), log)
defer goplsConn.Close()
log.Info("creating proxy")
// Create the proxy to sit between.
serverProxy, serverInit := proxy.NewServer(log, goplsServer, cache, diagnosticCache)
// Create templ server.
log.Info("creating templ server")
_, templConn, templClient := protocol.NewServer(context.Background(), serverProxy, templStream, log)
defer templConn.Close()
// Allow both the server and the client to initiate outbound requests.
clientInit(templClient)
serverInit(templClient)
// Start the web server if required.
if args.HTTPDebug != "" {
log.Info("starting debug http server", zap.String("addr", args.HTTPDebug))
h := httpdebug.NewHandler(log, serverProxy)
go func() {
if err := http.ListenAndServe(args.HTTPDebug, h); err != nil {
log.Error("web server failed", zap.Error(err))
}
}()
}
log.Info("listening")
select {
case <-ctx.Done():
log.Info("context closed")
case <-templConn.Done():
log.Info("templConn closed")
case <-goplsConn.Done():
log.Info("goplsConn closed")
}
log.Info("shutdown complete")
return
}