ttsc 0.13.0 → 0.14.0-dev.20260528.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -3
- package/cmd/ttscserver/main.go +56 -14
- package/cmd/ttscserver/run_lsp_forwards_execute_command_advertisement_flags_test.go +55 -0
- package/cmd/ttscserver/run_lsp_uses_native_plugin_manifest_source_test.go +45 -0
- package/driver/lsp.go +21 -1
- package/internal/lspserver/lsp_frame.go +30 -10
- package/internal/lspserver/lsp_native_plugin_source.go +380 -0
- package/internal/lspserver/lsp_plugin.go +16 -11
- package/internal/lspserver/lsp_proxy.go +1153 -73
- package/internal/lspserver/lsp_server.go +17 -5
- package/lib/TtscCompiler.d.ts +5 -4
- package/lib/TtscCompiler.js +56 -4
- package/lib/TtscCompiler.js.map +1 -1
- package/lib/compiler/internal/runBuild.js +149 -38
- package/lib/compiler/internal/runBuild.js.map +1 -1
- package/lib/compiler/internal/transformProjectInMemory.js +9 -7
- package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
- package/lib/flags/schema.d.ts +42 -43
- package/lib/flags/schema.js +35 -17
- package/lib/flags/schema.js.map +1 -1
- package/lib/launcher/internal/getCompilerVersionText.js +6 -14
- package/lib/launcher/internal/getCompilerVersionText.js.map +1 -1
- package/lib/launcher/internal/prepareExecution.d.ts +1 -0
- package/lib/launcher/internal/prepareExecution.js +31 -16
- package/lib/launcher/internal/prepareExecution.js.map +1 -1
- package/lib/launcher/internal/runTtscserver.d.ts +3 -1
- package/lib/launcher/internal/runTtscserver.js +88 -11
- package/lib/launcher/internal/runTtscserver.js.map +1 -1
- package/lib/launcher/internal/runTtsx.js +39 -26
- package/lib/launcher/internal/runTtsx.js.map +1 -1
- package/lib/structures/ITtscCompilerContext.d.ts +1 -1
- package/lib/structures/ITtscCompilerResult.d.ts +11 -0
- package/lib/structures/ITtscCompilerTransformation.d.ts +14 -3
- package/lib/structures/ITtscPlugin.d.ts +24 -15
- package/lib/structures/ITtscPluginCapabilities.d.ts +36 -14
- package/lib/structures/ITtscPluginContributor.d.ts +4 -2
- package/lib/structures/ITtscPluginFactoryContext.d.ts +3 -3
- package/lib/structures/ITtscProjectPluginConfig.d.ts +3 -3
- package/lib/structures/TtscPluginStage.d.ts +4 -3
- package/lib/structures/internal/ITtscLoadedNativePlugin.d.ts +7 -4
- package/package.json +10 -10
- package/shim/ast/lint.go +7 -1
- package/shim/checker/shim.go +3 -0
- package/shim/core/shim.go +11 -0
- package/src/TtscCompiler.ts +67 -4
- package/src/compiler/internal/runBuild.ts +209 -33
- package/src/compiler/internal/transformProjectInMemory.ts +10 -5
- package/src/flags/schema.ts +62 -43
- package/src/launcher/internal/getCompilerVersionText.ts +4 -13
- package/src/launcher/internal/prepareExecution.ts +31 -16
- package/src/launcher/internal/runTtscserver.ts +105 -11
- package/src/launcher/internal/runTtsx.ts +41 -29
- package/src/structures/ITtscCompilerContext.ts +1 -1
- package/src/structures/ITtscCompilerResult.ts +12 -0
- package/src/structures/ITtscCompilerTransformation.ts +15 -3
- package/src/structures/ITtscPlugin.ts +24 -15
- package/src/structures/ITtscPluginCapabilities.ts +38 -14
- package/src/structures/ITtscPluginContributor.ts +4 -2
- package/src/structures/ITtscPluginFactoryContext.ts +3 -3
- package/src/structures/ITtscProjectPluginConfig.ts +3 -3
- package/src/structures/TtscPluginStage.ts +4 -3
- package/src/structures/internal/ITtscLoadedNativePlugin.ts +7 -4
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
package lspserver
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"context"
|
|
6
|
+
"encoding/json"
|
|
7
|
+
"fmt"
|
|
8
|
+
"io"
|
|
9
|
+
"os"
|
|
10
|
+
"os/exec"
|
|
11
|
+
"strings"
|
|
12
|
+
"sync"
|
|
13
|
+
"time"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
const nativePluginCommandTimeout = 30 * time.Second
|
|
17
|
+
const nativePluginCommandStdoutLimit = 4 * 1024 * 1024
|
|
18
|
+
const nativePluginCommandStderrLimit = 1024 * 1024
|
|
19
|
+
|
|
20
|
+
// NativePluginManifest is the JSON shape the JavaScript ttscserver launcher
|
|
21
|
+
// passes through TTSC_LSP_PLUGINS_JSON after running normal project plugin
|
|
22
|
+
// discovery and source-plugin builds.
|
|
23
|
+
type NativePluginManifest struct {
|
|
24
|
+
Plugins []NativePluginConfigEntry `json:"plugins"`
|
|
25
|
+
LSPPlugins []NativeLSPPluginEntry `json:"lspPlugins"`
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// NativePluginConfigEntry mirrors the compact sidecar protocol used by
|
|
29
|
+
// --plugins-json. It intentionally excludes host-only fields such as binary.
|
|
30
|
+
type NativePluginConfigEntry struct {
|
|
31
|
+
Config map[string]any `json:"config"`
|
|
32
|
+
Name string `json:"name"`
|
|
33
|
+
Stage string `json:"stage"`
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// NativeLSPPluginEntry names one built sidecar that opted into the LSP
|
|
37
|
+
// protocol through its JavaScript descriptor capabilities.
|
|
38
|
+
type NativeLSPPluginEntry struct {
|
|
39
|
+
Binary string `json:"binary"`
|
|
40
|
+
Name string `json:"name,omitempty"`
|
|
41
|
+
Stage string `json:"stage,omitempty"`
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// NativePluginSourceOptions configures a sidecar-backed PluginSource.
|
|
45
|
+
type NativePluginSourceOptions struct {
|
|
46
|
+
Cwd string
|
|
47
|
+
Err io.Writer
|
|
48
|
+
ManifestJSON string
|
|
49
|
+
Tsconfig string
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// NativePluginSource implements PluginSource by delegating to native sidecars
|
|
53
|
+
// that explicitly support ttsc's LSP subcommands.
|
|
54
|
+
type NativePluginSource struct {
|
|
55
|
+
cwd string
|
|
56
|
+
err io.Writer
|
|
57
|
+
plugins []NativeLSPPluginEntry
|
|
58
|
+
pluginsJSON string
|
|
59
|
+
tsconfig string
|
|
60
|
+
|
|
61
|
+
commandIDs []string
|
|
62
|
+
codeActionKinds []string
|
|
63
|
+
owners map[string]NativeLSPPluginEntry
|
|
64
|
+
logMu sync.Mutex
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
type limitedBuffer struct {
|
|
68
|
+
buf bytes.Buffer
|
|
69
|
+
limit int
|
|
70
|
+
truncated bool
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
func (b *limitedBuffer) Write(p []byte) (int, error) {
|
|
74
|
+
remaining := b.limit - b.Len()
|
|
75
|
+
if remaining <= 0 {
|
|
76
|
+
b.truncated = true
|
|
77
|
+
return len(p), nil
|
|
78
|
+
}
|
|
79
|
+
if len(p) > remaining {
|
|
80
|
+
_, _ = b.buf.Write(p[:remaining])
|
|
81
|
+
b.truncated = true
|
|
82
|
+
return len(p), nil
|
|
83
|
+
}
|
|
84
|
+
_, _ = b.buf.Write(p)
|
|
85
|
+
return len(p), nil
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
func (b *limitedBuffer) Len() int {
|
|
89
|
+
return b.buf.Len()
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
func (b *limitedBuffer) String() string {
|
|
93
|
+
return b.buf.String()
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
func (b *limitedBuffer) Bytes() []byte {
|
|
97
|
+
return b.buf.Bytes()
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// NewNativePluginSource parses a launcher-produced manifest and discovers the
|
|
101
|
+
// command ids owned by every LSP-capable sidecar.
|
|
102
|
+
func NewNativePluginSource(opts NativePluginSourceOptions) (*NativePluginSource, error) {
|
|
103
|
+
var manifest NativePluginManifest
|
|
104
|
+
if strings.TrimSpace(opts.ManifestJSON) != "" {
|
|
105
|
+
if err := json.Unmarshal([]byte(opts.ManifestJSON), &manifest); err != nil {
|
|
106
|
+
return nil, fmt.Errorf("ttscserver: invalid TTSC_LSP_PLUGINS_JSON: %w", err)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
pluginsJSON, err := json.Marshal(manifest.Plugins)
|
|
110
|
+
if err != nil {
|
|
111
|
+
return nil, fmt.Errorf("ttscserver: encode plugin manifest: %w", err)
|
|
112
|
+
}
|
|
113
|
+
source := &NativePluginSource{
|
|
114
|
+
cwd: opts.Cwd,
|
|
115
|
+
err: opts.Err,
|
|
116
|
+
plugins: manifest.LSPPlugins,
|
|
117
|
+
pluginsJSON: string(pluginsJSON),
|
|
118
|
+
tsconfig: opts.Tsconfig,
|
|
119
|
+
owners: map[string]NativeLSPPluginEntry{},
|
|
120
|
+
}
|
|
121
|
+
source.discoverCommandIDs()
|
|
122
|
+
return source, nil
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Diagnostics asks every LSP-capable sidecar for diagnostics matching doc.URI.
|
|
126
|
+
func (s *NativePluginSource) Diagnostics(doc LSPDocumentVersion) []LSPDiagnostic {
|
|
127
|
+
if s == nil || doc.URI == "" {
|
|
128
|
+
return nil
|
|
129
|
+
}
|
|
130
|
+
var out []LSPDiagnostic
|
|
131
|
+
for _, plugin := range s.plugins {
|
|
132
|
+
body, err := s.run(plugin, "lsp-diagnostics", "--uri="+doc.URI)
|
|
133
|
+
if err != nil {
|
|
134
|
+
s.log("%v", err)
|
|
135
|
+
continue
|
|
136
|
+
}
|
|
137
|
+
var diagnostics []LSPDiagnostic
|
|
138
|
+
if err := json.Unmarshal(body, &diagnostics); err != nil {
|
|
139
|
+
s.log("ttscserver: %s lsp-diagnostics returned invalid JSON: %v", pluginLabel(plugin), err)
|
|
140
|
+
continue
|
|
141
|
+
}
|
|
142
|
+
out = append(out, diagnostics...)
|
|
143
|
+
}
|
|
144
|
+
return out
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// CodeActions asks every LSP-capable sidecar for actions matching the request.
|
|
148
|
+
func (s *NativePluginSource) CodeActions(uri string, rng LSPRange, ctx LSPCodeActionContext) []LSPCodeAction {
|
|
149
|
+
if s == nil || uri == "" {
|
|
150
|
+
return nil
|
|
151
|
+
}
|
|
152
|
+
rangeJSON, _ := json.Marshal(rng)
|
|
153
|
+
contextJSON, _ := json.Marshal(ctx)
|
|
154
|
+
var out []LSPCodeAction
|
|
155
|
+
for _, plugin := range s.plugins {
|
|
156
|
+
body, err := s.run(
|
|
157
|
+
plugin,
|
|
158
|
+
"lsp-code-actions",
|
|
159
|
+
"--uri="+uri,
|
|
160
|
+
"--range-json="+string(rangeJSON),
|
|
161
|
+
"--context-json="+string(contextJSON),
|
|
162
|
+
)
|
|
163
|
+
if err != nil {
|
|
164
|
+
s.log("%v", err)
|
|
165
|
+
continue
|
|
166
|
+
}
|
|
167
|
+
var actions []LSPCodeAction
|
|
168
|
+
if err := json.Unmarshal(body, &actions); err != nil {
|
|
169
|
+
s.log("ttscserver: %s lsp-code-actions returned invalid JSON: %v", pluginLabel(plugin), err)
|
|
170
|
+
continue
|
|
171
|
+
}
|
|
172
|
+
for _, action := range actions {
|
|
173
|
+
if hasDirectCodeActionEdit(action.Edit) {
|
|
174
|
+
s.log("ttscserver: %s returned direct LSP edit for action %q; command-backed actions are required", pluginLabel(plugin), action.Title)
|
|
175
|
+
continue
|
|
176
|
+
}
|
|
177
|
+
if action.Command == nil {
|
|
178
|
+
s.log("ttscserver: %s returned commandless LSP action %q; command-backed actions are required", pluginLabel(plugin), action.Title)
|
|
179
|
+
continue
|
|
180
|
+
}
|
|
181
|
+
if !s.pluginOwnsCommand(plugin, action.Command.Command) {
|
|
182
|
+
s.log("ttscserver: %s returned unowned LSP command %q", pluginLabel(plugin), action.Command.Command)
|
|
183
|
+
continue
|
|
184
|
+
}
|
|
185
|
+
out = append(out, action)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return out
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ExecuteCommand routes a ttsc-owned workspace command to the sidecar that
|
|
192
|
+
// advertised it through lsp-command-ids.
|
|
193
|
+
func (s *NativePluginSource) ExecuteCommand(command string, args []json.RawMessage) (*LSPWorkspaceEdit, error) {
|
|
194
|
+
if s == nil {
|
|
195
|
+
return nil, ErrCommandNotHandled
|
|
196
|
+
}
|
|
197
|
+
plugin, ok := s.owners[command]
|
|
198
|
+
if !ok {
|
|
199
|
+
return nil, ErrCommandNotHandled
|
|
200
|
+
}
|
|
201
|
+
argsJSON, _ := json.Marshal(args)
|
|
202
|
+
body, err := s.run(
|
|
203
|
+
plugin,
|
|
204
|
+
"lsp-execute-command",
|
|
205
|
+
"--command="+command,
|
|
206
|
+
"--arguments-json="+string(argsJSON),
|
|
207
|
+
)
|
|
208
|
+
if err != nil {
|
|
209
|
+
return nil, err
|
|
210
|
+
}
|
|
211
|
+
if bytes.Equal(bytes.TrimSpace(body), []byte("null")) {
|
|
212
|
+
return nil, nil
|
|
213
|
+
}
|
|
214
|
+
edit, err := decodeNativeLSPWorkspaceEdit(plugin, body)
|
|
215
|
+
if err != nil {
|
|
216
|
+
return nil, err
|
|
217
|
+
}
|
|
218
|
+
return edit, nil
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
func decodeNativeLSPWorkspaceEdit(plugin NativeLSPPluginEntry, body []byte) (*LSPWorkspaceEdit, error) {
|
|
222
|
+
var probe struct {
|
|
223
|
+
Changes json.RawMessage `json:"changes,omitempty"`
|
|
224
|
+
DocumentChanges json.RawMessage `json:"documentChanges,omitempty"`
|
|
225
|
+
}
|
|
226
|
+
if err := json.Unmarshal(body, &probe); err != nil {
|
|
227
|
+
return nil, fmt.Errorf("ttscserver: %s lsp-execute-command returned invalid JSON: %w", pluginLabel(plugin), err)
|
|
228
|
+
}
|
|
229
|
+
if probe.DocumentChanges != nil && !bytes.Equal(bytes.TrimSpace(probe.DocumentChanges), []byte("null")) {
|
|
230
|
+
return nil, fmt.Errorf("ttscserver: %s lsp-execute-command returned unsupported WorkspaceEdit.documentChanges; return changes or null", pluginLabel(plugin))
|
|
231
|
+
}
|
|
232
|
+
var edit LSPWorkspaceEdit
|
|
233
|
+
if err := json.Unmarshal(body, &edit); err != nil {
|
|
234
|
+
return nil, fmt.Errorf("ttscserver: %s lsp-execute-command returned invalid WorkspaceEdit: %w", pluginLabel(plugin), err)
|
|
235
|
+
}
|
|
236
|
+
return &edit, nil
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// CommandIDs returns the command ids discovered at source construction time.
|
|
240
|
+
func (s *NativePluginSource) CommandIDs() []string {
|
|
241
|
+
if s == nil || len(s.commandIDs) == 0 {
|
|
242
|
+
return nil
|
|
243
|
+
}
|
|
244
|
+
out := make([]string, len(s.commandIDs))
|
|
245
|
+
copy(out, s.commandIDs)
|
|
246
|
+
return out
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// CodeActionKinds returns the action kinds discovered from LSP-capable sidecars.
|
|
250
|
+
func (s *NativePluginSource) CodeActionKinds() []string {
|
|
251
|
+
if s == nil || len(s.codeActionKinds) == 0 {
|
|
252
|
+
return nil
|
|
253
|
+
}
|
|
254
|
+
out := make([]string, len(s.codeActionKinds))
|
|
255
|
+
copy(out, s.codeActionKinds)
|
|
256
|
+
return out
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
func (s *NativePluginSource) discoverCommandIDs() {
|
|
260
|
+
seen := map[string]struct{}{}
|
|
261
|
+
kindSeen := map[string]struct{}{}
|
|
262
|
+
for _, plugin := range s.plugins {
|
|
263
|
+
body, err := s.run(plugin, "lsp-command-ids")
|
|
264
|
+
if err != nil {
|
|
265
|
+
s.log("%v", err)
|
|
266
|
+
continue
|
|
267
|
+
}
|
|
268
|
+
var ids []string
|
|
269
|
+
if err := json.Unmarshal(body, &ids); err != nil {
|
|
270
|
+
s.log("ttscserver: %s lsp-command-ids returned invalid JSON: %v", pluginLabel(plugin), err)
|
|
271
|
+
continue
|
|
272
|
+
}
|
|
273
|
+
for _, id := range ids {
|
|
274
|
+
if id == "" {
|
|
275
|
+
continue
|
|
276
|
+
}
|
|
277
|
+
if _, ok := seen[id]; ok {
|
|
278
|
+
s.log("ttscserver: duplicate LSP command id %q from %s ignored", id, pluginLabel(plugin))
|
|
279
|
+
continue
|
|
280
|
+
}
|
|
281
|
+
seen[id] = struct{}{}
|
|
282
|
+
s.commandIDs = append(s.commandIDs, id)
|
|
283
|
+
s.owners[id] = plugin
|
|
284
|
+
}
|
|
285
|
+
kindBody, kindErr := s.run(plugin, "lsp-code-action-kinds")
|
|
286
|
+
if kindErr != nil {
|
|
287
|
+
s.log("%v", kindErr)
|
|
288
|
+
continue
|
|
289
|
+
}
|
|
290
|
+
var kinds []string
|
|
291
|
+
if err := json.Unmarshal(kindBody, &kinds); err != nil {
|
|
292
|
+
s.log("ttscserver: %s lsp-code-action-kinds returned invalid JSON: %v", pluginLabel(plugin), err)
|
|
293
|
+
continue
|
|
294
|
+
}
|
|
295
|
+
for _, kind := range kinds {
|
|
296
|
+
if kind == "" {
|
|
297
|
+
continue
|
|
298
|
+
}
|
|
299
|
+
if _, ok := kindSeen[kind]; ok {
|
|
300
|
+
continue
|
|
301
|
+
}
|
|
302
|
+
kindSeen[kind] = struct{}{}
|
|
303
|
+
s.codeActionKinds = append(s.codeActionKinds, kind)
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
func (s *NativePluginSource) pluginOwnsCommand(plugin NativeLSPPluginEntry, command string) bool {
|
|
309
|
+
if strings.TrimSpace(command) == "" {
|
|
310
|
+
return false
|
|
311
|
+
}
|
|
312
|
+
owner, ok := s.owners[command]
|
|
313
|
+
if !ok {
|
|
314
|
+
return false
|
|
315
|
+
}
|
|
316
|
+
return owner.Binary == plugin.Binary && owner.Name == plugin.Name && owner.Stage == plugin.Stage
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
func (s *NativePluginSource) run(plugin NativeLSPPluginEntry, command string, args ...string) ([]byte, error) {
|
|
320
|
+
if strings.TrimSpace(plugin.Binary) == "" {
|
|
321
|
+
return nil, fmt.Errorf("ttscserver: %s has no binary", pluginLabel(plugin))
|
|
322
|
+
}
|
|
323
|
+
ctx, cancel := context.WithTimeout(context.Background(), nativePluginCommandTimeout)
|
|
324
|
+
defer cancel()
|
|
325
|
+
allArgs := []string{
|
|
326
|
+
command,
|
|
327
|
+
"--cwd=" + s.cwd,
|
|
328
|
+
"--tsconfig=" + s.tsconfig,
|
|
329
|
+
"--plugins-json=" + s.pluginsJSON,
|
|
330
|
+
}
|
|
331
|
+
allArgs = append(allArgs, args...)
|
|
332
|
+
cmd := exec.CommandContext(ctx, plugin.Binary, allArgs...)
|
|
333
|
+
cmd.Dir = s.cwd
|
|
334
|
+
cmd.Env = os.Environ()
|
|
335
|
+
stdout := limitedBuffer{limit: nativePluginCommandStdoutLimit}
|
|
336
|
+
stderr := limitedBuffer{limit: nativePluginCommandStderrLimit}
|
|
337
|
+
cmd.Stdout = &stdout
|
|
338
|
+
cmd.Stderr = &stderr
|
|
339
|
+
err := cmd.Run()
|
|
340
|
+
if ctx.Err() != nil {
|
|
341
|
+
return nil, fmt.Errorf("ttscserver: %s %s timed out", pluginLabel(plugin), command)
|
|
342
|
+
}
|
|
343
|
+
if err != nil {
|
|
344
|
+
msg := strings.TrimSpace(stderr.String())
|
|
345
|
+
if msg == "" {
|
|
346
|
+
msg = err.Error()
|
|
347
|
+
} else if stderr.truncated || stderr.Len() >= nativePluginCommandStderrLimit {
|
|
348
|
+
msg += " (stderr truncated)"
|
|
349
|
+
}
|
|
350
|
+
return nil, fmt.Errorf("ttscserver: %s %s failed: %s", pluginLabel(plugin), command, msg)
|
|
351
|
+
}
|
|
352
|
+
if stdout.truncated {
|
|
353
|
+
return nil, fmt.Errorf("ttscserver: %s %s produced more than %d bytes on stdout", pluginLabel(plugin), command, nativePluginCommandStdoutLimit)
|
|
354
|
+
}
|
|
355
|
+
return bytes.TrimSpace(stdout.Bytes()), nil
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
func hasDirectCodeActionEdit(edit json.RawMessage) bool {
|
|
359
|
+
trimmed := bytes.TrimSpace(edit)
|
|
360
|
+
return len(trimmed) > 0 && !bytes.Equal(trimmed, []byte("null"))
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
func (s *NativePluginSource) log(format string, args ...any) {
|
|
364
|
+
if s == nil || s.err == nil {
|
|
365
|
+
return
|
|
366
|
+
}
|
|
367
|
+
s.logMu.Lock()
|
|
368
|
+
defer s.logMu.Unlock()
|
|
369
|
+
fmt.Fprintf(s.err, format+"\n", args...)
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
func pluginLabel(plugin NativeLSPPluginEntry) string {
|
|
373
|
+
if plugin.Name != "" {
|
|
374
|
+
return plugin.Name
|
|
375
|
+
}
|
|
376
|
+
if plugin.Binary != "" {
|
|
377
|
+
return plugin.Binary
|
|
378
|
+
}
|
|
379
|
+
return "plugin"
|
|
380
|
+
}
|
|
@@ -46,8 +46,10 @@ type LSPDiagnostic struct {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
// LSPCodeAction is the minimal Code Action shape ttscserver returns from
|
|
49
|
-
// the textDocument/codeAction handler.
|
|
50
|
-
//
|
|
49
|
+
// the textDocument/codeAction handler. Sidecar-backed NativePluginSource
|
|
50
|
+
// accepts only command-driven actions and drops non-null direct edits; custom
|
|
51
|
+
// in-process PluginSource implementations may still return Edit when they own
|
|
52
|
+
// that policy.
|
|
51
53
|
type LSPCodeAction struct {
|
|
52
54
|
Title string `json:"title"`
|
|
53
55
|
Kind string `json:"kind,omitempty"`
|
|
@@ -64,8 +66,9 @@ type LSPCommand struct {
|
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
// LSPCodeActionContext mirrors the context object the editor sends with
|
|
67
|
-
// textDocument/codeAction. Only
|
|
68
|
-
//
|
|
69
|
+
// textDocument/codeAction. The proxy inspects Only to decide local routing, and
|
|
70
|
+
// plugin sources may inspect Diagnostics, Only, and TriggerKind when filtering
|
|
71
|
+
// their own actions.
|
|
69
72
|
type LSPCodeActionContext struct {
|
|
70
73
|
Diagnostics []json.RawMessage `json:"diagnostics,omitempty"`
|
|
71
74
|
Only []string `json:"only,omitempty"`
|
|
@@ -112,13 +115,15 @@ type PluginSource interface {
|
|
|
112
115
|
Diagnostics(doc LSPDocumentVersion) []LSPDiagnostic
|
|
113
116
|
|
|
114
117
|
// CodeActions contributes additional actions for the given range. The
|
|
115
|
-
// proxy appends them to
|
|
118
|
+
// proxy appends them to upstream responses, or answers locally when
|
|
119
|
+
// the request is plugin-only / upstream advertised no provider.
|
|
116
120
|
CodeActions(uri string, rng LSPRange, ctx LSPCodeActionContext) []LSPCodeAction
|
|
117
121
|
|
|
118
|
-
// ExecuteCommand handles workspace/executeCommand requests whose
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
+
// ExecuteCommand handles workspace/executeCommand requests whose command id
|
|
123
|
+
// appears in CommandIDs. A nil edit means the command ran but produced no
|
|
124
|
+
// workspace changes; a non-nil error surfaces as an LSP error response.
|
|
125
|
+
// Returning ErrCommandNotHandled for an advertised id is also treated as an
|
|
126
|
+
// error by the proxy because advertised commands are owned locally.
|
|
122
127
|
ExecuteCommand(command string, args []json.RawMessage) (*LSPWorkspaceEdit, error)
|
|
123
128
|
|
|
124
129
|
// CommandIDs lists the workspace command ids ttsc handles locally so
|
|
@@ -128,8 +133,8 @@ type PluginSource interface {
|
|
|
128
133
|
|
|
129
134
|
// NullPluginSource is the zero-contribution PluginSource used when the
|
|
130
135
|
// LSP server is hosted without any ttsc plugin pipeline (smoke tests,
|
|
131
|
-
// docs build).
|
|
132
|
-
// exercises its merge paths even with no plugin activity.
|
|
136
|
+
// docs build). It returns no diagnostics/actions and no command ids so the
|
|
137
|
+
// proxy still exercises its merge paths even with no plugin activity.
|
|
133
138
|
type NullPluginSource struct{}
|
|
134
139
|
|
|
135
140
|
// Diagnostics returns no plugin diagnostics.
|