ttsc 0.20.1 → 0.22.0
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/cmd/ttscserver/main.go +79 -3
- package/cmd/ttscserver/run_lsp_uses_native_plugin_manifest_source_test.go +96 -16
- package/driver/lsp.go +4 -0
- package/internal/lspserver/lsp_document_diagnostics_omits_cached_project_when_current_result_has_none_test.go +48 -0
- package/internal/lspserver/lsp_executable_project_input_change_requests_launcher_restart_test.go +98 -0
- package/internal/lspserver/lsp_external_watched_change_retains_program_test.go +53 -0
- package/internal/lspserver/lsp_hints_refresh_scheduled_by_editor_events_test.go +9 -0
- package/internal/lspserver/lsp_native_plugin_source.go +171 -55
- package/internal/lspserver/lsp_native_resident.go +110 -9
- package/internal/lspserver/lsp_plugin.go +6 -0
- package/internal/lspserver/lsp_project_context_transport_identity_test.go +43 -0
- package/internal/lspserver/lsp_project_diagnostics.go +235 -0
- package/internal/lspserver/lsp_project_diagnostics_capability_is_independent_test.go +67 -0
- package/internal/lspserver/lsp_project_diagnostics_program_input_overlap_refreshes_all_producers_test.go +78 -0
- package/internal/lspserver/lsp_project_diagnostics_refresh_accumulates_owner_freshness_test.go +65 -0
- package/internal/lspserver/lsp_project_diagnostics_refresh_keeps_each_producer_last_good_test.go +90 -0
- package/internal/lspserver/lsp_project_diagnostics_refreshes_only_input_owners_test.go +62 -0
- package/internal/lspserver/lsp_project_diagnostics_resident_unsupported_falls_back_test.go +123 -0
- package/internal/lspserver/lsp_project_diagnostics_resume_preserves_generation_test.go +52 -0
- package/internal/lspserver/lsp_project_diagnostics_translates_relative_client_project_test.go +90 -0
- package/internal/lspserver/lsp_project_input_ownership_resolves_declared_spellings_test.go +95 -0
- package/internal/lspserver/lsp_project_input_registration.go +504 -0
- package/internal/lspserver/lsp_project_input_registration_rechecks_selection_baseline_test.go +71 -0
- package/internal/lspserver/lsp_project_input_registration_reconciles_rejected_pending_request_test.go +268 -0
- package/internal/lspserver/lsp_project_input_registration_shapes_patterns_test.go +94 -0
- package/internal/lspserver/lsp_project_input_watchers_anchor_missing_paths_test.go +48 -0
- package/internal/lspserver/lsp_project_inputs.go +944 -0
- package/internal/lspserver/lsp_project_inputs_match_missing_exact_and_glob_test.go +129 -0
- package/internal/lspserver/lsp_project_inputs_reject_invalid_and_foreign_snapshots_test.go +110 -0
- package/internal/lspserver/lsp_proxy.go +591 -28
- package/internal/lspserver/lsp_reload_directories_compare_immediate_topology_test.go +188 -0
- package/internal/lspserver/lsp_reload_file_fingerprint_preserves_raw_symlink_target_test.go +47 -0
- package/internal/lspserver/lsp_resident_falls_back_when_serve_unsupported_test.go +1 -1
- package/internal/lspserver/lsp_resident_invalidation_broadcasts_program_input_overlap_test.go +60 -0
- package/internal/lspserver/lsp_resident_invalidation_targets_project_input_owners_test.go +51 -0
- package/internal/lspserver/lsp_same_transport_entries_are_deduplicated_test.go +66 -0
- package/lib/compiler/internal/project/readProjectConfig.d.ts +5 -3
- package/lib/compiler/internal/project/readProjectConfig.js +46 -14
- package/lib/compiler/internal/project/readProjectConfig.js.map +1 -1
- package/lib/compiler/internal/residentCheckProcess.d.ts +46 -0
- package/lib/compiler/internal/residentCheckProcess.js +227 -0
- package/lib/compiler/internal/residentCheckProcess.js.map +1 -0
- package/lib/compiler/internal/resolveEmittedJavaScript.d.ts +2 -1
- package/lib/compiler/internal/resolveEmittedJavaScript.js +35 -18
- package/lib/compiler/internal/resolveEmittedJavaScript.js.map +1 -1
- package/lib/compiler/internal/runBuild.d.ts +64 -1
- package/lib/compiler/internal/runBuild.js +484 -7
- package/lib/compiler/internal/runBuild.js.map +1 -1
- package/lib/compiler/internal/runSingleFileEmit.js +1 -0
- package/lib/compiler/internal/runSingleFileEmit.js.map +1 -1
- package/lib/flags/parser.js +13 -17
- package/lib/flags/parser.js.map +1 -1
- package/lib/flags/schema.d.ts +10 -3
- package/lib/flags/schema.js +99 -0
- package/lib/flags/schema.js.map +1 -1
- package/lib/internal/projectInputPathIdentity.d.ts +23 -0
- package/lib/internal/projectInputPathIdentity.js +222 -0
- package/lib/internal/projectInputPathIdentity.js.map +1 -0
- package/lib/launcher/internal/runTtsc.d.ts +17 -0
- package/lib/launcher/internal/runTtsc.js +121 -88
- package/lib/launcher/internal/runTtsc.js.map +1 -1
- package/lib/launcher/internal/runTtscserver.d.ts +13 -1
- package/lib/launcher/internal/runTtscserver.js +415 -45
- package/lib/launcher/internal/runTtscserver.js.map +1 -1
- package/lib/launcher/internal/singleFileOutput.d.ts +16 -0
- package/lib/launcher/internal/singleFileOutput.js +103 -0
- package/lib/launcher/internal/singleFileOutput.js.map +1 -0
- package/lib/launcher/internal/watchTopology.d.ts +214 -2
- package/lib/launcher/internal/watchTopology.js +1560 -41
- package/lib/launcher/internal/watchTopology.js.map +1 -1
- package/lib/plugin/internal/loadProjectPlugins.d.ts +1 -0
- package/lib/plugin/internal/loadProjectPlugins.js +5 -0
- package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
- package/lib/structures/ITtscPluginCapabilities.d.ts +31 -0
- package/lib/structures/internal/ITtscProjectInputSnapshot.d.ts +29 -0
- package/lib/structures/internal/ITtscProjectInputSnapshot.js +3 -0
- package/lib/structures/internal/ITtscProjectInputSnapshot.js.map +1 -0
- package/lib/structures/internal/TtscSingleFileEmitOptions.d.ts +5 -0
- package/package.json +8 -8
- package/src/compiler/internal/project/readProjectConfig.ts +62 -8
- package/src/compiler/internal/residentCheckProcess.ts +279 -0
- package/src/compiler/internal/resolveEmittedJavaScript.ts +43 -21
- package/src/compiler/internal/runBuild.ts +728 -12
- package/src/compiler/internal/runSingleFileEmit.ts +1 -0
- package/src/flags/parser.ts +17 -17
- package/src/flags/schema.ts +111 -3
- package/src/internal/projectInputPathIdentity.ts +273 -0
- package/src/launcher/internal/runTtsc.ts +141 -117
- package/src/launcher/internal/runTtscserver.ts +561 -54
- package/src/launcher/internal/singleFileOutput.ts +133 -0
- package/src/launcher/internal/watchTopology.ts +2008 -59
- package/src/plugin/internal/loadProjectPlugins.ts +8 -0
- package/src/structures/ITtscPluginCapabilities.ts +34 -0
- package/src/structures/internal/ITtscProjectInputSnapshot.ts +29 -0
- package/src/structures/internal/TtscSingleFileEmitOptions.ts +5 -0
|
@@ -10,9 +10,11 @@ import (
|
|
|
10
10
|
"net/url"
|
|
11
11
|
"os"
|
|
12
12
|
"path/filepath"
|
|
13
|
+
"sort"
|
|
13
14
|
"strings"
|
|
14
15
|
"sync"
|
|
15
16
|
"sync/atomic"
|
|
17
|
+
"time"
|
|
16
18
|
"unicode/utf16"
|
|
17
19
|
"unicode/utf8"
|
|
18
20
|
)
|
|
@@ -24,22 +26,32 @@ import (
|
|
|
24
26
|
// fallback.
|
|
25
27
|
var ErrCommandNotHandled = errors.New("lsp: command not handled by ttsc")
|
|
26
28
|
|
|
29
|
+
// ErrLSPPluginSelectionChanged ends the current native host after a watched
|
|
30
|
+
// config dependency changes which source plugins the JavaScript launcher must
|
|
31
|
+
// rebuild. A fresh editor-managed process reruns descriptor discovery before it
|
|
32
|
+
// creates the next LSP stream.
|
|
33
|
+
var ErrLSPPluginSelectionChanged = errors.New(
|
|
34
|
+
"ttscserver: plugin selection input changed; restart required",
|
|
35
|
+
)
|
|
36
|
+
|
|
27
37
|
const (
|
|
28
|
-
methodPublishDiagnostics
|
|
29
|
-
methodInitialize
|
|
30
|
-
methodDidOpen
|
|
31
|
-
methodDidChange
|
|
32
|
-
methodDidSave
|
|
33
|
-
methodDidClose
|
|
34
|
-
methodCodeAction
|
|
35
|
-
methodExecuteCommand
|
|
36
|
-
methodCancelRequest
|
|
37
|
-
methodFormatting
|
|
38
|
-
methodDocumentSymbol
|
|
39
|
-
methodReferences
|
|
40
|
-
methodCompletion
|
|
41
|
-
methodCompletionResolve
|
|
42
|
-
|
|
38
|
+
methodPublishDiagnostics = "textDocument/publishDiagnostics"
|
|
39
|
+
methodInitialize = "initialize"
|
|
40
|
+
methodDidOpen = "textDocument/didOpen"
|
|
41
|
+
methodDidChange = "textDocument/didChange"
|
|
42
|
+
methodDidSave = "textDocument/didSave"
|
|
43
|
+
methodDidClose = "textDocument/didClose"
|
|
44
|
+
methodCodeAction = "textDocument/codeAction"
|
|
45
|
+
methodExecuteCommand = "workspace/executeCommand"
|
|
46
|
+
methodCancelRequest = "$/cancelRequest"
|
|
47
|
+
methodFormatting = "textDocument/formatting"
|
|
48
|
+
methodDocumentSymbol = "textDocument/documentSymbol"
|
|
49
|
+
methodReferences = "textDocument/references"
|
|
50
|
+
methodCompletion = "textDocument/completion"
|
|
51
|
+
methodCompletionResolve = "completionItem/resolve"
|
|
52
|
+
methodInitialized = "initialized"
|
|
53
|
+
methodExit = "exit"
|
|
54
|
+
methodPluginSelectionChanged = "ttsc/pluginSelectionChanged"
|
|
43
55
|
|
|
44
56
|
// methodDidChangeWatchedFiles is the only notification an editor sends for a
|
|
45
57
|
// file it does not have open: a tsconfig edit, a generated file, a branch
|
|
@@ -138,7 +150,9 @@ type Proxy struct {
|
|
|
138
150
|
pendingAugmentingActions map[string]struct{}
|
|
139
151
|
pendingLocalActions map[string]struct{}
|
|
140
152
|
pendingCommands map[string]struct{}
|
|
153
|
+
pendingClientRequests map[string]func(Envelope)
|
|
141
154
|
pendingInitialize map[string]struct{}
|
|
155
|
+
clientRequestSequence atomic.Uint64
|
|
142
156
|
|
|
143
157
|
capabilityMu sync.Mutex
|
|
144
158
|
upstreamCodeActionProvider bool
|
|
@@ -153,6 +167,8 @@ type Proxy struct {
|
|
|
153
167
|
// without a restart, so the proxy says so once per character.
|
|
154
168
|
advertisedCompletionTriggers map[string]struct{}
|
|
155
169
|
reportedCompletionTriggers map[string]struct{}
|
|
170
|
+
projectInputWatchDynamic bool
|
|
171
|
+
projectInputWatchRelative bool
|
|
156
172
|
|
|
157
173
|
diagnosticsMu sync.Mutex
|
|
158
174
|
upstreamDiagnostics map[string]cachedDiagnostics
|
|
@@ -174,6 +190,25 @@ type Proxy struct {
|
|
|
174
190
|
// handler falls back to a disk read. Guarded by diagnosticsMu like the
|
|
175
191
|
// other per-uri document state.
|
|
176
192
|
documentText map[string]string
|
|
193
|
+
|
|
194
|
+
projectRefreshMu sync.Mutex
|
|
195
|
+
projectRefreshTimer *time.Timer
|
|
196
|
+
projectDiagnosticsRefresh coalescingRefresh
|
|
197
|
+
pendingProjectDiagnosticGeneration uint64
|
|
198
|
+
projectDiagnosticRefreshPending bool
|
|
199
|
+
pendingProjectDiagnosticAllOwners bool
|
|
200
|
+
pendingProjectDiagnosticOwners map[string]struct{}
|
|
201
|
+
|
|
202
|
+
projectInputWatchMu sync.Mutex
|
|
203
|
+
projectInputWatchReady bool
|
|
204
|
+
projectInputWatchDesired projectInputWatchRegistration
|
|
205
|
+
projectInputWatchActive projectInputWatchRegistration
|
|
206
|
+
projectInputWatchStaleIDs []string
|
|
207
|
+
projectInputWatchPending bool
|
|
208
|
+
projectInputWatchFailedSignature string
|
|
209
|
+
projectInputWatchUnregisterRetryBlocked bool
|
|
210
|
+
projectInputWatchWarningSent bool
|
|
211
|
+
projectInputWatchRegistrationSequence uint64
|
|
177
212
|
}
|
|
178
213
|
|
|
179
214
|
type pendingCodeActionRequest struct {
|
|
@@ -217,6 +252,7 @@ func NewProxy(opts ProxyOptions) *Proxy {
|
|
|
217
252
|
pendingAugmentingActions: make(map[string]struct{}),
|
|
218
253
|
pendingLocalActions: make(map[string]struct{}),
|
|
219
254
|
pendingCommands: make(map[string]struct{}),
|
|
255
|
+
pendingClientRequests: make(map[string]func(Envelope)),
|
|
220
256
|
pendingInitialize: make(map[string]struct{}),
|
|
221
257
|
upstreamCodeActionProvider: true,
|
|
222
258
|
upstreamDiagnostics: make(map[string]cachedDiagnostics),
|
|
@@ -236,6 +272,14 @@ func NewProxy(opts ProxyOptions) *Proxy {
|
|
|
236
272
|
if observed, ok := source.(completionHintObserverSource); ok {
|
|
237
273
|
observed.SetCompletionHintsObserver(proxy.completionHintsRefreshed)
|
|
238
274
|
}
|
|
275
|
+
type projectInputObserverSource interface {
|
|
276
|
+
SetProjectInputsObserver(func())
|
|
277
|
+
}
|
|
278
|
+
if observed, ok := source.(projectInputObserverSource); ok {
|
|
279
|
+
observed.SetProjectInputsObserver(func() {
|
|
280
|
+
go proxy.projectInputsRefreshed()
|
|
281
|
+
})
|
|
282
|
+
}
|
|
239
283
|
return proxy
|
|
240
284
|
}
|
|
241
285
|
|
|
@@ -245,6 +289,7 @@ func NewProxy(opts ProxyOptions) *Proxy {
|
|
|
245
289
|
// pipe write fails. ErrFrameClosed and context.Canceled are folded into
|
|
246
290
|
// a nil result so editor shutdown does not look like a crash.
|
|
247
291
|
func (p *Proxy) Run(ctx context.Context) error {
|
|
292
|
+
defer p.stopProjectDiagnosticRefresh()
|
|
248
293
|
errCh := make(chan error, 2)
|
|
249
294
|
go func() { errCh <- p.pumpEditorToUpstream(ctx) }()
|
|
250
295
|
go func() { errCh <- p.pumpUpstreamToEditor(ctx) }()
|
|
@@ -308,6 +353,9 @@ func (p *Proxy) pumpEditorToUpstream(_ context.Context) error {
|
|
|
308
353
|
if err := p.writeUpstreamFrame(constrainInitializePositionEncoding(env, body)); err != nil {
|
|
309
354
|
return err
|
|
310
355
|
}
|
|
356
|
+
if env.IsNotification() && env.Method == methodInitialized {
|
|
357
|
+
go p.projectInputWatchInitialized()
|
|
358
|
+
}
|
|
311
359
|
}
|
|
312
360
|
}
|
|
313
361
|
|
|
@@ -324,6 +372,9 @@ func (p *Proxy) closeUpstreamInput() {
|
|
|
324
372
|
// handleEditorEnvelope returns true if the envelope was fully processed
|
|
325
373
|
// locally (responded to without forwarding upstream).
|
|
326
374
|
func (p *Proxy) handleEditorEnvelope(env Envelope, body []byte) (bool, error) {
|
|
375
|
+
if env.IsResponse() && p.handleClientRequestResponse(env) {
|
|
376
|
+
return true, nil
|
|
377
|
+
}
|
|
327
378
|
switch env.Method {
|
|
328
379
|
case methodInitialize:
|
|
329
380
|
if env.IsRequest() {
|
|
@@ -342,6 +393,7 @@ func (p *Proxy) handleEditorEnvelope(env Envelope, body []byte) (bool, error) {
|
|
|
342
393
|
p.invalidateResidentPlugins(env)
|
|
343
394
|
p.refreshPluginCompletionHints()
|
|
344
395
|
p.publishPluginDiagnosticsForDocumentNotification(env)
|
|
396
|
+
p.resumePendingProjectDiagnosticRefresh()
|
|
345
397
|
}
|
|
346
398
|
case methodDidChangeConfiguration:
|
|
347
399
|
if env.IsNotification() {
|
|
@@ -350,6 +402,7 @@ func (p *Proxy) handleEditorEnvelope(env Envelope, body []byte) (bool, error) {
|
|
|
350
402
|
// invalidateForWatchedFileChanges, which knows whether anything actually
|
|
351
403
|
// changed.
|
|
352
404
|
p.refreshPluginCompletionHints()
|
|
405
|
+
p.refreshProjectInputs()
|
|
353
406
|
}
|
|
354
407
|
case methodDidChange:
|
|
355
408
|
if env.IsNotification() {
|
|
@@ -369,6 +422,7 @@ func (p *Proxy) handleEditorEnvelope(env Envelope, body []byte) (bool, error) {
|
|
|
369
422
|
if env.IsNotification() {
|
|
370
423
|
p.evictDocumentText(env)
|
|
371
424
|
p.clearDocumentDiagnostics(env)
|
|
425
|
+
p.resumePendingProjectDiagnosticRefresh()
|
|
372
426
|
}
|
|
373
427
|
case methodDidChangeWatchedFiles:
|
|
374
428
|
if env.IsNotification() {
|
|
@@ -537,6 +591,24 @@ func (p *Proxy) rememberInitializeRequest(env Envelope) {
|
|
|
537
591
|
if key == "" {
|
|
538
592
|
return
|
|
539
593
|
}
|
|
594
|
+
var params struct {
|
|
595
|
+
Capabilities struct {
|
|
596
|
+
Workspace struct {
|
|
597
|
+
DidChangeWatchedFiles struct {
|
|
598
|
+
DynamicRegistration bool `json:"dynamicRegistration"`
|
|
599
|
+
RelativePatternSupport bool `json:"relativePatternSupport"`
|
|
600
|
+
} `json:"didChangeWatchedFiles"`
|
|
601
|
+
} `json:"workspace"`
|
|
602
|
+
} `json:"capabilities"`
|
|
603
|
+
}
|
|
604
|
+
if json.Unmarshal(env.Params, ¶ms) == nil {
|
|
605
|
+
p.capabilityMu.Lock()
|
|
606
|
+
p.projectInputWatchDynamic =
|
|
607
|
+
params.Capabilities.Workspace.DidChangeWatchedFiles.DynamicRegistration
|
|
608
|
+
p.projectInputWatchRelative =
|
|
609
|
+
params.Capabilities.Workspace.DidChangeWatchedFiles.RelativePatternSupport
|
|
610
|
+
p.capabilityMu.Unlock()
|
|
611
|
+
}
|
|
540
612
|
p.pendingMu.Lock()
|
|
541
613
|
defer p.pendingMu.Unlock()
|
|
542
614
|
p.pendingInitialize[key] = struct{}{}
|
|
@@ -1260,8 +1332,20 @@ func (p *Proxy) publishMergedPluginDiagnostics(uri string, version *int, adoptCa
|
|
|
1260
1332
|
if ok {
|
|
1261
1333
|
p.reportAsyncError(p.writePublishDiagnosticsIfCurrent(uri, version, merged, generation))
|
|
1262
1334
|
}
|
|
1263
|
-
if diagnostics.Project != nil {
|
|
1264
|
-
p.
|
|
1335
|
+
if diagnostics.Project != nil && !p.hasDirtyDocuments() {
|
|
1336
|
+
writeResult, err := p.writeProjectDiagnosticsIfCurrent(
|
|
1337
|
+
diagnostics.Project,
|
|
1338
|
+
projectGeneration,
|
|
1339
|
+
false,
|
|
1340
|
+
)
|
|
1341
|
+
p.reportAsyncError(err)
|
|
1342
|
+
if writeResult.frameWritten &&
|
|
1343
|
+
p.recordPendingProjectDiagnosticOwnersRefreshed(
|
|
1344
|
+
projectGeneration,
|
|
1345
|
+
diagnostics.projectUpdatedProducers,
|
|
1346
|
+
) {
|
|
1347
|
+
p.completePendingProjectDiagnosticRefresh(projectGeneration)
|
|
1348
|
+
}
|
|
1265
1349
|
}
|
|
1266
1350
|
}
|
|
1267
1351
|
|
|
@@ -1283,9 +1367,13 @@ func (p *Proxy) writePublishDiagnosticsIfCurrent(uri string, version *int, diagn
|
|
|
1283
1367
|
return WriteFrame(p.editorOut, body)
|
|
1284
1368
|
}
|
|
1285
1369
|
|
|
1286
|
-
func (p *Proxy) writeProjectDiagnosticsIfCurrent(
|
|
1370
|
+
func (p *Proxy) writeProjectDiagnosticsIfCurrent(
|
|
1371
|
+
publication *LSPProjectDiagnostics,
|
|
1372
|
+
generation uint64,
|
|
1373
|
+
publishEmpty bool,
|
|
1374
|
+
) (projectDiagnosticsWriteResult, error) {
|
|
1287
1375
|
if publication == nil || publication.URI == "" {
|
|
1288
|
-
return nil
|
|
1376
|
+
return projectDiagnosticsWriteResult{}, nil
|
|
1289
1377
|
}
|
|
1290
1378
|
diagnostics := append([]LSPDiagnostic(nil), publication.Diagnostics...)
|
|
1291
1379
|
for index := range diagnostics {
|
|
@@ -1296,8 +1384,9 @@ func (p *Proxy) writeProjectDiagnosticsIfCurrent(publication *LSPProjectDiagnost
|
|
|
1296
1384
|
defer p.writeMu.Unlock()
|
|
1297
1385
|
p.diagnosticsMu.Lock()
|
|
1298
1386
|
defer p.diagnosticsMu.Unlock()
|
|
1299
|
-
if p.projectDiagnosticGeneration != generation
|
|
1300
|
-
|
|
1387
|
+
if p.projectDiagnosticGeneration != generation ||
|
|
1388
|
+
len(p.dirtyDocuments) != 0 {
|
|
1389
|
+
return projectDiagnosticsWriteResult{}, nil
|
|
1301
1390
|
}
|
|
1302
1391
|
|
|
1303
1392
|
previousURI := p.projectDiagnosticsURI
|
|
@@ -1310,19 +1399,33 @@ func (p *Proxy) writeProjectDiagnosticsIfCurrent(publication *LSPProjectDiagnost
|
|
|
1310
1399
|
p.editorOut,
|
|
1311
1400
|
p.publishDiagnosticsBody(previousURI, nil, p.mergedDiagnosticsLocked(previousURI)),
|
|
1312
1401
|
); err != nil {
|
|
1313
|
-
return err
|
|
1402
|
+
return projectDiagnosticsWriteResult{}, err
|
|
1314
1403
|
}
|
|
1315
1404
|
}
|
|
1316
1405
|
|
|
1317
1406
|
p.projectDiagnosticsURI = publication.URI
|
|
1318
1407
|
p.projectDiagnostics = cachedDiagnostics{diagnostics: copyRawDiagnostics(rawDiagnostics)}
|
|
1319
|
-
if len(rawDiagnostics) == 0 &&
|
|
1320
|
-
|
|
1408
|
+
if len(rawDiagnostics) == 0 &&
|
|
1409
|
+
!publishEmpty &&
|
|
1410
|
+
!previousHadDiagnostics &&
|
|
1411
|
+
!changedURI {
|
|
1412
|
+
return projectDiagnosticsWriteResult{accepted: true}, nil
|
|
1321
1413
|
}
|
|
1322
|
-
|
|
1414
|
+
if err := WriteFrame(
|
|
1323
1415
|
p.editorOut,
|
|
1324
1416
|
p.publishDiagnosticsBody(publication.URI, nil, p.mergedDiagnosticsLocked(publication.URI)),
|
|
1325
|
-
)
|
|
1417
|
+
); err != nil {
|
|
1418
|
+
return projectDiagnosticsWriteResult{}, err
|
|
1419
|
+
}
|
|
1420
|
+
return projectDiagnosticsWriteResult{
|
|
1421
|
+
accepted: true,
|
|
1422
|
+
frameWritten: true,
|
|
1423
|
+
}, nil
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
type projectDiagnosticsWriteResult struct {
|
|
1427
|
+
accepted bool
|
|
1428
|
+
frameWritten bool
|
|
1326
1429
|
}
|
|
1327
1430
|
|
|
1328
1431
|
func marshalLSPDiagnostics(diagnostics []LSPDiagnostic) []json.RawMessage {
|
|
@@ -2255,6 +2358,44 @@ func (p *Proxy) invalidateResidentPluginsForURIs(uris ...string) {
|
|
|
2255
2358
|
}
|
|
2256
2359
|
}
|
|
2257
2360
|
|
|
2361
|
+
type watchedResidentInvalidator interface {
|
|
2362
|
+
InvalidateResidentProgramsForWatchedChanges(
|
|
2363
|
+
changedURIs []string,
|
|
2364
|
+
externalURIs []string,
|
|
2365
|
+
)
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
type ownedWatchedResidentInvalidator interface {
|
|
2369
|
+
InvalidateResidentProgramsForOwnedWatchedChanges(
|
|
2370
|
+
changedURIs []string,
|
|
2371
|
+
externalURIs []string,
|
|
2372
|
+
externalOwners map[string][]string,
|
|
2373
|
+
)
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
func (p *Proxy) invalidateResidentPluginsForWatchedChanges(
|
|
2377
|
+
changedURIs []string,
|
|
2378
|
+
externalURIs []string,
|
|
2379
|
+
externalOwners map[string][]string,
|
|
2380
|
+
) {
|
|
2381
|
+
if source, ok := p.source.(ownedWatchedResidentInvalidator); ok {
|
|
2382
|
+
source.InvalidateResidentProgramsForOwnedWatchedChanges(
|
|
2383
|
+
changedURIs,
|
|
2384
|
+
externalURIs,
|
|
2385
|
+
externalOwners,
|
|
2386
|
+
)
|
|
2387
|
+
return
|
|
2388
|
+
}
|
|
2389
|
+
if source, ok := p.source.(watchedResidentInvalidator); ok {
|
|
2390
|
+
source.InvalidateResidentProgramsForWatchedChanges(
|
|
2391
|
+
changedURIs,
|
|
2392
|
+
externalURIs,
|
|
2393
|
+
)
|
|
2394
|
+
return
|
|
2395
|
+
}
|
|
2396
|
+
p.invalidateResidentPluginsForURIs(changedURIs...)
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2258
2399
|
// LSP FileChangeType. Only `changed` names an edit to a file whose place in the
|
|
2259
2400
|
// project is unchanged; created and deleted both reshape the root set, which a
|
|
2260
2401
|
// per-file UpdateProgram cannot express.
|
|
@@ -2302,13 +2443,51 @@ func (p *Proxy) invalidateForWatchedFileChanges(env Envelope) error {
|
|
|
2302
2443
|
// per plugin, each loading its own Program.
|
|
2303
2444
|
return nil
|
|
2304
2445
|
}
|
|
2446
|
+
for _, change := range params.Changes {
|
|
2447
|
+
if p.projectInputReloadMatchesChange(change.URI, change.Type) {
|
|
2448
|
+
if err := p.writePluginSelectionChanged(); err != nil {
|
|
2449
|
+
return err
|
|
2450
|
+
}
|
|
2451
|
+
return ErrLSPPluginSelectionChanged
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2305
2454
|
uris := make([]string, 0, len(params.Changes))
|
|
2455
|
+
externalURIs := make([]string, 0, len(params.Changes))
|
|
2456
|
+
externalSet := make(map[string]struct{}, len(params.Changes))
|
|
2457
|
+
externalOwners := make(map[string][]string, len(params.Changes))
|
|
2458
|
+
affectedProjectDiagnosticOwners := map[string]struct{}{}
|
|
2459
|
+
allProjectDiagnosticOwners := false
|
|
2306
2460
|
deleted := make([]string, 0, len(params.Changes))
|
|
2307
2461
|
localizable := true
|
|
2308
2462
|
for _, change := range params.Changes {
|
|
2309
2463
|
if change.URI != "" && change.Type != nil && *change.Type == fileChangeTypeDeleted {
|
|
2310
2464
|
deleted = append(deleted, change.URI)
|
|
2311
2465
|
}
|
|
2466
|
+
ownerScope, matched := p.projectInputOwnerScope(change.URI)
|
|
2467
|
+
if matched {
|
|
2468
|
+
ownerScope = projectDiagnosticScopeForWatchedInput(
|
|
2469
|
+
change.URI,
|
|
2470
|
+
ownerScope,
|
|
2471
|
+
)
|
|
2472
|
+
externalSet[change.URI] = struct{}{}
|
|
2473
|
+
if ownerScope.all {
|
|
2474
|
+
allProjectDiagnosticOwners = true
|
|
2475
|
+
externalOwners[change.URI] = nil
|
|
2476
|
+
} else {
|
|
2477
|
+
owners := ownerScope.list()
|
|
2478
|
+
externalOwners[change.URI] = owners
|
|
2479
|
+
for _, owner := range owners {
|
|
2480
|
+
affectedProjectDiagnosticOwners[owner] = struct{}{}
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
if externalWatchedChangeRetainsProgram(change.URI, change.Type) {
|
|
2484
|
+
externalURIs = append(externalURIs, change.URI)
|
|
2485
|
+
uris = append(uris, change.URI)
|
|
2486
|
+
} else {
|
|
2487
|
+
localizable = false
|
|
2488
|
+
}
|
|
2489
|
+
continue
|
|
2490
|
+
}
|
|
2312
2491
|
if !watchedFileChangeIsLocalizable(change.URI, change.Type) {
|
|
2313
2492
|
localizable = false
|
|
2314
2493
|
continue
|
|
@@ -2319,11 +2498,24 @@ func (p *Proxy) invalidateForWatchedFileChanges(env Envelope) error {
|
|
|
2319
2498
|
if !localizable || len(uris) == 0 {
|
|
2320
2499
|
p.invalidateResidentPluginsForURIs()
|
|
2321
2500
|
} else {
|
|
2322
|
-
p.
|
|
2501
|
+
p.invalidateResidentPluginsForWatchedChanges(
|
|
2502
|
+
uris,
|
|
2503
|
+
externalURIs,
|
|
2504
|
+
externalOwners,
|
|
2505
|
+
)
|
|
2323
2506
|
}
|
|
2324
2507
|
// A completion corpus projects the whole Program, so it cannot be invalidated
|
|
2325
2508
|
// per URI the way the resident daemon's is: any reported change can change it.
|
|
2326
|
-
|
|
2509
|
+
if len(externalSet) > 0 || watchedChangesMayAffectProgram(params.Changes) {
|
|
2510
|
+
p.refreshPluginCompletionHints()
|
|
2511
|
+
}
|
|
2512
|
+
if len(externalSet) > 0 {
|
|
2513
|
+
p.refreshProjectInputs()
|
|
2514
|
+
p.scheduleProjectDiagnosticRefresh(projectDiagnosticOwnerScope{
|
|
2515
|
+
all: allProjectDiagnosticOwners,
|
|
2516
|
+
owners: affectedProjectDiagnosticOwners,
|
|
2517
|
+
})
|
|
2518
|
+
}
|
|
2327
2519
|
for _, uri := range deleted {
|
|
2328
2520
|
if err := p.withdrawPluginDiagnosticsForDeletedDocument(uri); err != nil {
|
|
2329
2521
|
return err
|
|
@@ -2375,6 +2567,377 @@ func watchedFileChangeIsLocalizable(uri string, changeType *int) bool {
|
|
|
2375
2567
|
}
|
|
2376
2568
|
}
|
|
2377
2569
|
|
|
2570
|
+
type projectInputMatcher interface {
|
|
2571
|
+
ProjectInputMatchesURI(string) bool
|
|
2572
|
+
}
|
|
2573
|
+
|
|
2574
|
+
type projectInputReloadMatcher interface {
|
|
2575
|
+
ProjectInputReloadMatchesURI(string) bool
|
|
2576
|
+
}
|
|
2577
|
+
|
|
2578
|
+
type projectInputReloadChangeMatcher interface {
|
|
2579
|
+
ProjectInputReloadMatchesChange(string, *int) bool
|
|
2580
|
+
}
|
|
2581
|
+
|
|
2582
|
+
type projectInputOwnerMatcher interface {
|
|
2583
|
+
ProjectInputOwnersForURI(string) []string
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2586
|
+
type projectDiagnosticOwnerScope struct {
|
|
2587
|
+
all bool
|
|
2588
|
+
owners map[string]struct{}
|
|
2589
|
+
}
|
|
2590
|
+
|
|
2591
|
+
func projectDiagnosticScopeForWatchedInput(
|
|
2592
|
+
uri string,
|
|
2593
|
+
scope projectDiagnosticOwnerScope,
|
|
2594
|
+
) projectDiagnosticOwnerScope {
|
|
2595
|
+
if watchedURIHasProgramInputExtension(uri) {
|
|
2596
|
+
return projectDiagnosticOwnerScope{all: true}
|
|
2597
|
+
}
|
|
2598
|
+
return scope
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
func (scope projectDiagnosticOwnerScope) list() []string {
|
|
2602
|
+
owners := make([]string, 0, len(scope.owners))
|
|
2603
|
+
for owner := range scope.owners {
|
|
2604
|
+
owners = append(owners, owner)
|
|
2605
|
+
}
|
|
2606
|
+
sort.Strings(owners)
|
|
2607
|
+
return owners
|
|
2608
|
+
}
|
|
2609
|
+
|
|
2610
|
+
func (p *Proxy) projectInputOwnerScope(
|
|
2611
|
+
uri string,
|
|
2612
|
+
) (projectDiagnosticOwnerScope, bool) {
|
|
2613
|
+
if source, ok := p.source.(projectInputOwnerMatcher); ok {
|
|
2614
|
+
owners := source.ProjectInputOwnersForURI(uri)
|
|
2615
|
+
if len(owners) != 0 {
|
|
2616
|
+
scope := projectDiagnosticOwnerScope{owners: map[string]struct{}{}}
|
|
2617
|
+
for _, owner := range owners {
|
|
2618
|
+
scope.owners[owner] = struct{}{}
|
|
2619
|
+
}
|
|
2620
|
+
return scope, true
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
source, ok := p.source.(projectInputMatcher)
|
|
2624
|
+
if !ok || !source.ProjectInputMatchesURI(uri) {
|
|
2625
|
+
return projectDiagnosticOwnerScope{}, false
|
|
2626
|
+
}
|
|
2627
|
+
return projectDiagnosticOwnerScope{all: true}, true
|
|
2628
|
+
}
|
|
2629
|
+
|
|
2630
|
+
func (p *Proxy) projectInputReloadMatchesChange(
|
|
2631
|
+
uri string,
|
|
2632
|
+
changeType *int,
|
|
2633
|
+
) bool {
|
|
2634
|
+
if source, ok := p.source.(projectInputReloadChangeMatcher); ok {
|
|
2635
|
+
return source.ProjectInputReloadMatchesChange(uri, changeType)
|
|
2636
|
+
}
|
|
2637
|
+
source, ok := p.source.(projectInputReloadMatcher)
|
|
2638
|
+
return ok && source.ProjectInputReloadMatchesURI(uri)
|
|
2639
|
+
}
|
|
2640
|
+
|
|
2641
|
+
func (p *Proxy) writePluginSelectionChanged() error {
|
|
2642
|
+
params, err := json.Marshal(map[string]string{
|
|
2643
|
+
"reason": "projectInputChanged",
|
|
2644
|
+
})
|
|
2645
|
+
if err != nil {
|
|
2646
|
+
return err
|
|
2647
|
+
}
|
|
2648
|
+
body, err := json.Marshal(Envelope{
|
|
2649
|
+
JSONRPC: "2.0",
|
|
2650
|
+
Method: methodPluginSelectionChanged,
|
|
2651
|
+
Params: params,
|
|
2652
|
+
})
|
|
2653
|
+
if err != nil {
|
|
2654
|
+
return err
|
|
2655
|
+
}
|
|
2656
|
+
return p.writeEditorFrame(body)
|
|
2657
|
+
}
|
|
2658
|
+
|
|
2659
|
+
func (p *Proxy) refreshProjectInputs() {
|
|
2660
|
+
type projectInputRefresher interface {
|
|
2661
|
+
RefreshProjectInputs()
|
|
2662
|
+
}
|
|
2663
|
+
if source, ok := p.source.(projectInputRefresher); ok {
|
|
2664
|
+
source.RefreshProjectInputs()
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
|
|
2668
|
+
type projectDiagnosticsSource interface {
|
|
2669
|
+
ProjectDiagnostics() *LSPProjectDiagnostics
|
|
2670
|
+
}
|
|
2671
|
+
|
|
2672
|
+
type ownedProjectDiagnosticsSource interface {
|
|
2673
|
+
ProjectDiagnosticsForOwners([]string) projectDiagnosticsRefreshResult
|
|
2674
|
+
}
|
|
2675
|
+
|
|
2676
|
+
// scheduleProjectDiagnosticRefresh debounces external-input events and
|
|
2677
|
+
// invalidates the prior generation immediately. One in-flight computation may
|
|
2678
|
+
// finish, but its generation can no longer publish after a newer event.
|
|
2679
|
+
func (p *Proxy) scheduleProjectDiagnosticRefresh(
|
|
2680
|
+
scope projectDiagnosticOwnerScope,
|
|
2681
|
+
) {
|
|
2682
|
+
if _, owned := p.source.(ownedProjectDiagnosticsSource); !owned {
|
|
2683
|
+
if _, legacy := p.source.(projectDiagnosticsSource); !legacy {
|
|
2684
|
+
return
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2687
|
+
if !scope.all && len(scope.owners) == 0 {
|
|
2688
|
+
if _, owned := p.source.(ownedProjectDiagnosticsSource); !owned {
|
|
2689
|
+
return
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
// Generation ownership and pending scope form one transaction. Resume uses
|
|
2693
|
+
// the same diagnostics -> refresh lock order, so neither path can overwrite
|
|
2694
|
+
// the other's newer pending generation between the two assignments.
|
|
2695
|
+
p.diagnosticsMu.Lock()
|
|
2696
|
+
p.projectRefreshMu.Lock()
|
|
2697
|
+
p.projectDiagnosticGeneration++
|
|
2698
|
+
generation := p.projectDiagnosticGeneration
|
|
2699
|
+
p.pendingProjectDiagnosticGeneration = generation
|
|
2700
|
+
p.projectDiagnosticRefreshPending = true
|
|
2701
|
+
if scope.all {
|
|
2702
|
+
p.pendingProjectDiagnosticAllOwners = true
|
|
2703
|
+
p.pendingProjectDiagnosticOwners = nil
|
|
2704
|
+
} else if !p.pendingProjectDiagnosticAllOwners {
|
|
2705
|
+
if p.pendingProjectDiagnosticOwners == nil {
|
|
2706
|
+
p.pendingProjectDiagnosticOwners = map[string]struct{}{}
|
|
2707
|
+
}
|
|
2708
|
+
for owner := range scope.owners {
|
|
2709
|
+
p.pendingProjectDiagnosticOwners[owner] = struct{}{}
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
p.armProjectDiagnosticRefreshLocked()
|
|
2713
|
+
p.projectRefreshMu.Unlock()
|
|
2714
|
+
p.diagnosticsMu.Unlock()
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2717
|
+
func (p *Proxy) armProjectDiagnosticRefreshLocked() {
|
|
2718
|
+
if p.projectRefreshTimer != nil {
|
|
2719
|
+
p.projectRefreshTimer.Stop()
|
|
2720
|
+
}
|
|
2721
|
+
p.projectRefreshTimer = time.AfterFunc(60*time.Millisecond, func() {
|
|
2722
|
+
p.projectDiagnosticsRefresh.schedule(
|
|
2723
|
+
p.refreshProjectDiagnostics,
|
|
2724
|
+
)
|
|
2725
|
+
})
|
|
2726
|
+
}
|
|
2727
|
+
|
|
2728
|
+
func (p *Proxy) pendingProjectDiagnosticScopeLocked() projectDiagnosticOwnerScope {
|
|
2729
|
+
scope := projectDiagnosticOwnerScope{
|
|
2730
|
+
all: p.pendingProjectDiagnosticAllOwners,
|
|
2731
|
+
owners: map[string]struct{}{},
|
|
2732
|
+
}
|
|
2733
|
+
for owner := range p.pendingProjectDiagnosticOwners {
|
|
2734
|
+
scope.owners[owner] = struct{}{}
|
|
2735
|
+
}
|
|
2736
|
+
return scope
|
|
2737
|
+
}
|
|
2738
|
+
|
|
2739
|
+
func (p *Proxy) recordPendingProjectDiagnosticOwnersRefreshed(
|
|
2740
|
+
generation uint64,
|
|
2741
|
+
refreshed map[string]struct{},
|
|
2742
|
+
) bool {
|
|
2743
|
+
p.projectRefreshMu.Lock()
|
|
2744
|
+
defer p.projectRefreshMu.Unlock()
|
|
2745
|
+
if !p.projectDiagnosticRefreshPending ||
|
|
2746
|
+
generation < p.pendingProjectDiagnosticGeneration ||
|
|
2747
|
+
p.pendingProjectDiagnosticAllOwners ||
|
|
2748
|
+
len(p.pendingProjectDiagnosticOwners) == 0 {
|
|
2749
|
+
return false
|
|
2750
|
+
}
|
|
2751
|
+
for owner := range refreshed {
|
|
2752
|
+
delete(p.pendingProjectDiagnosticOwners, owner)
|
|
2753
|
+
}
|
|
2754
|
+
return len(p.pendingProjectDiagnosticOwners) == 0
|
|
2755
|
+
}
|
|
2756
|
+
|
|
2757
|
+
func (p *Proxy) refreshProjectDiagnostics(uint64) {
|
|
2758
|
+
p.projectRefreshMu.Lock()
|
|
2759
|
+
generation := p.pendingProjectDiagnosticGeneration
|
|
2760
|
+
pending := p.projectDiagnosticRefreshPending
|
|
2761
|
+
scope := p.pendingProjectDiagnosticScopeLocked()
|
|
2762
|
+
p.projectRefreshMu.Unlock()
|
|
2763
|
+
if !pending {
|
|
2764
|
+
return
|
|
2765
|
+
}
|
|
2766
|
+
if p.hasDirtyDocuments() {
|
|
2767
|
+
return
|
|
2768
|
+
}
|
|
2769
|
+
refresh := projectDiagnosticsRefreshResult{complete: true}
|
|
2770
|
+
if source, ok := p.source.(ownedProjectDiagnosticsSource); ok {
|
|
2771
|
+
owners := scope.list()
|
|
2772
|
+
if scope.all {
|
|
2773
|
+
owners = nil
|
|
2774
|
+
}
|
|
2775
|
+
refresh = source.ProjectDiagnosticsForOwners(owners)
|
|
2776
|
+
} else if source, ok := p.source.(projectDiagnosticsSource); ok {
|
|
2777
|
+
refresh.publication = source.ProjectDiagnostics()
|
|
2778
|
+
refresh.complete =
|
|
2779
|
+
refresh.publication != nil && refresh.publication.URI != ""
|
|
2780
|
+
refresh.selected = 1
|
|
2781
|
+
} else {
|
|
2782
|
+
return
|
|
2783
|
+
}
|
|
2784
|
+
if refresh.selected == 0 {
|
|
2785
|
+
if refresh.complete {
|
|
2786
|
+
p.completePendingProjectDiagnosticRefresh(generation)
|
|
2787
|
+
}
|
|
2788
|
+
return
|
|
2789
|
+
}
|
|
2790
|
+
if refresh.publication == nil || refresh.publication.URI == "" {
|
|
2791
|
+
return
|
|
2792
|
+
}
|
|
2793
|
+
// The editor can retain a project diagnostic from before this proxy
|
|
2794
|
+
// generation. An external-input refresh therefore publishes an explicit
|
|
2795
|
+
// empty replacement even when the in-process cache was already empty.
|
|
2796
|
+
writeResult, err := p.writeProjectDiagnosticsIfCurrent(
|
|
2797
|
+
refresh.publication,
|
|
2798
|
+
generation,
|
|
2799
|
+
true,
|
|
2800
|
+
)
|
|
2801
|
+
p.reportAsyncError(err)
|
|
2802
|
+
if err != nil || p.hasDirtyDocuments() {
|
|
2803
|
+
return
|
|
2804
|
+
}
|
|
2805
|
+
if writeResult.frameWritten {
|
|
2806
|
+
ownersComplete :=
|
|
2807
|
+
!scope.all &&
|
|
2808
|
+
p.recordPendingProjectDiagnosticOwnersRefreshed(
|
|
2809
|
+
generation,
|
|
2810
|
+
refresh.refreshed,
|
|
2811
|
+
)
|
|
2812
|
+
if refresh.complete || ownersComplete {
|
|
2813
|
+
p.completePendingProjectDiagnosticRefresh(generation)
|
|
2814
|
+
}
|
|
2815
|
+
return
|
|
2816
|
+
}
|
|
2817
|
+
// A document diagnostics request may have advanced the project generation
|
|
2818
|
+
// without publishing project diagnostics, for example when parse diagnostics
|
|
2819
|
+
// caused the sidecar to omit its project result. Retain and rerun the direct
|
|
2820
|
+
// refresh instead of treating that rejected write as a successful clear.
|
|
2821
|
+
if !writeResult.accepted {
|
|
2822
|
+
p.resumePendingProjectDiagnosticRefresh()
|
|
2823
|
+
}
|
|
2824
|
+
}
|
|
2825
|
+
|
|
2826
|
+
func (p *Proxy) completePendingProjectDiagnosticRefresh(generation uint64) {
|
|
2827
|
+
p.projectRefreshMu.Lock()
|
|
2828
|
+
defer p.projectRefreshMu.Unlock()
|
|
2829
|
+
if !p.projectDiagnosticRefreshPending ||
|
|
2830
|
+
generation < p.pendingProjectDiagnosticGeneration {
|
|
2831
|
+
return
|
|
2832
|
+
}
|
|
2833
|
+
p.projectDiagnosticRefreshPending = false
|
|
2834
|
+
p.pendingProjectDiagnosticAllOwners = false
|
|
2835
|
+
p.pendingProjectDiagnosticOwners = nil
|
|
2836
|
+
if p.projectRefreshTimer != nil {
|
|
2837
|
+
p.projectRefreshTimer.Stop()
|
|
2838
|
+
p.projectRefreshTimer = nil
|
|
2839
|
+
}
|
|
2840
|
+
}
|
|
2841
|
+
|
|
2842
|
+
func (p *Proxy) stopProjectDiagnosticRefresh() {
|
|
2843
|
+
p.projectRefreshMu.Lock()
|
|
2844
|
+
defer p.projectRefreshMu.Unlock()
|
|
2845
|
+
if p.projectRefreshTimer != nil {
|
|
2846
|
+
p.projectRefreshTimer.Stop()
|
|
2847
|
+
p.projectRefreshTimer = nil
|
|
2848
|
+
}
|
|
2849
|
+
p.projectDiagnosticRefreshPending = false
|
|
2850
|
+
p.pendingProjectDiagnosticAllOwners = false
|
|
2851
|
+
p.pendingProjectDiagnosticOwners = nil
|
|
2852
|
+
}
|
|
2853
|
+
|
|
2854
|
+
func (p *Proxy) resumePendingProjectDiagnosticRefresh() {
|
|
2855
|
+
p.diagnosticsMu.Lock()
|
|
2856
|
+
defer p.diagnosticsMu.Unlock()
|
|
2857
|
+
if len(p.dirtyDocuments) != 0 {
|
|
2858
|
+
return
|
|
2859
|
+
}
|
|
2860
|
+
p.projectRefreshMu.Lock()
|
|
2861
|
+
defer p.projectRefreshMu.Unlock()
|
|
2862
|
+
if p.projectDiagnosticRefreshPending {
|
|
2863
|
+
p.projectDiagnosticGeneration++
|
|
2864
|
+
p.pendingProjectDiagnosticGeneration = p.projectDiagnosticGeneration
|
|
2865
|
+
p.armProjectDiagnosticRefreshLocked()
|
|
2866
|
+
}
|
|
2867
|
+
}
|
|
2868
|
+
|
|
2869
|
+
func (p *Proxy) hasDirtyDocuments() bool {
|
|
2870
|
+
p.diagnosticsMu.Lock()
|
|
2871
|
+
defer p.diagnosticsMu.Unlock()
|
|
2872
|
+
return len(p.dirtyDocuments) > 0
|
|
2873
|
+
}
|
|
2874
|
+
|
|
2875
|
+
func watchedChangesMayAffectProgram(changes []struct {
|
|
2876
|
+
URI string `json:"uri"`
|
|
2877
|
+
Type *int `json:"type"`
|
|
2878
|
+
}) bool {
|
|
2879
|
+
for _, change := range changes {
|
|
2880
|
+
if isProjectConfigURI(change.URI) {
|
|
2881
|
+
return true
|
|
2882
|
+
}
|
|
2883
|
+
if watchedURIHasProgramInputExtension(change.URI) {
|
|
2884
|
+
return true
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
return false
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2890
|
+
func externalWatchedChangeRetainsProgram(
|
|
2891
|
+
uri string,
|
|
2892
|
+
changeType *int,
|
|
2893
|
+
) bool {
|
|
2894
|
+
if uri == "" || changeType == nil || isProjectConfigURI(uri) {
|
|
2895
|
+
return false
|
|
2896
|
+
}
|
|
2897
|
+
if _, ok := filePathFromURI(uri); !ok {
|
|
2898
|
+
return false
|
|
2899
|
+
}
|
|
2900
|
+
switch *changeType {
|
|
2901
|
+
case fileChangeTypeChanged:
|
|
2902
|
+
// Package metadata changes module resolution without changing the declared
|
|
2903
|
+
// input population. Ordinary data JSON remains a warm external update.
|
|
2904
|
+
return !watchedURIIsPackageJSON(uri)
|
|
2905
|
+
case fileChangeTypeCreated, fileChangeTypeDeleted:
|
|
2906
|
+
// A declared path may also satisfy a compiler root, module resolution, or
|
|
2907
|
+
// another compiler-recognized extension. Only those membership changes
|
|
2908
|
+
// cold-load the Program; data-only topology remains safely localizable.
|
|
2909
|
+
return !watchedURIHasProgramInputExtension(uri)
|
|
2910
|
+
default:
|
|
2911
|
+
return false
|
|
2912
|
+
}
|
|
2913
|
+
}
|
|
2914
|
+
|
|
2915
|
+
func watchedURIIsPackageJSON(uri string) bool {
|
|
2916
|
+
location, ok := filePathFromURI(uri)
|
|
2917
|
+
return ok && strings.EqualFold(filepath.Base(location), "package.json")
|
|
2918
|
+
}
|
|
2919
|
+
|
|
2920
|
+
func watchedURIHasProgramInputExtension(uri string) bool {
|
|
2921
|
+
if watchedURIHasProgramSourceExtension(uri) {
|
|
2922
|
+
return true
|
|
2923
|
+
}
|
|
2924
|
+
location, ok := filePathFromURI(uri)
|
|
2925
|
+
return ok && strings.EqualFold(filepath.Ext(location), ".json")
|
|
2926
|
+
}
|
|
2927
|
+
|
|
2928
|
+
func watchedURIHasProgramSourceExtension(uri string) bool {
|
|
2929
|
+
location, ok := filePathFromURI(uri)
|
|
2930
|
+
if !ok {
|
|
2931
|
+
return false
|
|
2932
|
+
}
|
|
2933
|
+
switch strings.ToLower(filepath.Ext(location)) {
|
|
2934
|
+
case ".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs":
|
|
2935
|
+
return true
|
|
2936
|
+
default:
|
|
2937
|
+
return false
|
|
2938
|
+
}
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2378
2941
|
// isProjectConfigURI reports whether uri names a TypeScript project
|
|
2379
2942
|
// configuration file — `tsconfig.json`, `jsconfig.json`, and the
|
|
2380
2943
|
// `tsconfig.<name>.json` spellings the VS Code client watches. Such an edit can
|