ttsc 0.14.0-dev.20260528.1 → 0.14.0-dev.20260529.2
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
CHANGED
|
@@ -2,17 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
|
|
5
|
-
[](https://github.com/samchon/ttsc/blob/master/LICENSE)
|
|
6
|
-
[](https://www.npmjs.com/package/ttsc)
|
|
7
|
-
[](https://www.npmjs.com/package/ttsc)
|
|
8
|
-
[](https://github.com/samchon/ttsc/actions?query=workflow%3Atest)
|
|
9
|
-
[](https://ttsc.dev/docs)
|
|
10
|
-
[](https://discord.gg/E94XhzrUCZ)
|
|
5
|
+
[](https://github.com/samchon/ttsc/blob/master/LICENSE) [](https://www.npmjs.com/package/ttsc) [](https://www.npmjs.com/package/ttsc) [](https://github.com/samchon/ttsc/actions?query=workflow%3Atest) [](https://ttsc.dev/docs) [](https://discord.gg/E94XhzrUCZ)
|
|
11
6
|
|
|
12
7
|
A `typescript-go` toolchain for compiler-powered plugins and type-safe execution.
|
|
13
8
|
|
|
14
|
-
Benchmarked on the VS Code fixture: up to **10x** faster type checks and a
|
|
15
|
-
**500x** faster measured lint pass.
|
|
9
|
+
Benchmarked on the VS Code fixture: up to **10x** faster type checks and a **500x** faster measured lint pass.
|
|
16
10
|
|
|
17
11
|
- **`ttsc`**: build, check, and transform.
|
|
18
12
|
- **`ttsx`**: execute TypeScript with type checking.
|
|
@@ -55,14 +49,23 @@ Rewrite source files in place with the `@ttsc/lint` format rules:
|
|
|
55
49
|
npx ttsc format
|
|
56
50
|
```
|
|
57
51
|
|
|
58
|
-
###
|
|
52
|
+
### VSCode Extension
|
|
59
53
|
|
|
60
|
-
Install the VS Code extension for live TypeScript-Go editor features plus
|
|
61
|
-
saved-state ttsc plugin diagnostics and actions:
|
|
54
|
+
Install the VS Code extension for live TypeScript-Go editor features plus saved-state ttsc plugin diagnostics and actions:
|
|
62
55
|
|
|
63
56
|
```bash
|
|
57
|
+
npm install -D ttsc @ttsc/lint @typescript/native-preview
|
|
64
58
|
npm install -D @ttsc/vscode
|
|
65
|
-
npx ttsc
|
|
59
|
+
npx @ttsc/vscode
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Then turn on format-on-save in `.vscode/settings.json`:
|
|
63
|
+
|
|
64
|
+
```jsonc
|
|
65
|
+
"[typescript][typescriptreact]": {
|
|
66
|
+
"editor.defaultFormatter": "samchon.ttsc",
|
|
67
|
+
"editor.formatOnSave": true
|
|
68
|
+
}
|
|
66
69
|
```
|
|
67
70
|
|
|
68
71
|
See [`@ttsc/vscode`](https://github.com/samchon/ttsc/tree/master/packages/vscode) for requirements, settings, and uninstall.
|
|
@@ -191,11 +194,7 @@ if (result.type === "success") {
|
|
|
191
194
|
}
|
|
192
195
|
```
|
|
193
196
|
|
|
194
|
-
See the [Programmatic API guide](https://ttsc.dev/docs/ttsc/api) for the full
|
|
195
|
-
lifecycle, plugin overrides, and patterns. For browser embedding, see
|
|
196
|
-
[`@ttsc/wasm`](https://github.com/samchon/ttsc/tree/master/packages/wasm) and
|
|
197
|
-
the higher-level [`@ttsc/playground`](https://github.com/samchon/ttsc/tree/master/packages/playground)
|
|
198
|
-
package.
|
|
197
|
+
See the [Programmatic API guide](https://ttsc.dev/docs/ttsc/api) for the full lifecycle, plugin overrides, and patterns. For browser embedding, see [`@ttsc/wasm`](https://github.com/samchon/ttsc/tree/master/packages/wasm) and the higher-level [`@ttsc/playground`](https://github.com/samchon/ttsc/tree/master/packages/playground) package.
|
|
199
198
|
|
|
200
199
|
### List of Plugins
|
|
201
200
|
|
|
@@ -191,6 +191,19 @@ func (s *NativePluginSource) CodeActions(uri string, rng LSPRange, ctx LSPCodeAc
|
|
|
191
191
|
// ExecuteCommand routes a ttsc-owned workspace command to the sidecar that
|
|
192
192
|
// advertised it through lsp-command-ids.
|
|
193
193
|
func (s *NativePluginSource) ExecuteCommand(command string, args []json.RawMessage) (*LSPWorkspaceEdit, error) {
|
|
194
|
+
return s.ExecuteCommandWithContent(command, args, "", false)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ExecuteCommandWithContent runs a ttsc-owned workspace command like
|
|
198
|
+
// ExecuteCommand, but when hasContent is true it asks the sidecar to format the
|
|
199
|
+
// supplied buffer text instead of the on-disk file. The buffer is passed by
|
|
200
|
+
// adding the --content-stdin flag and piping content to the sidecar's stdin, so
|
|
201
|
+
// the proxy can format dirty editor buffers (formatOnSave) without first writing
|
|
202
|
+
// them to disk. hasContent — not content != "" — gates the in-memory path: an
|
|
203
|
+
// empty buffer the user cleared is a valid document state and must still format
|
|
204
|
+
// in-memory (to a no-op) rather than falling through to stale disk content.
|
|
205
|
+
// Decoding of the returned WorkspaceEdit is identical to ExecuteCommand.
|
|
206
|
+
func (s *NativePluginSource) ExecuteCommandWithContent(command string, args []json.RawMessage, content string, hasContent bool) (*LSPWorkspaceEdit, error) {
|
|
194
207
|
if s == nil {
|
|
195
208
|
return nil, ErrCommandNotHandled
|
|
196
209
|
}
|
|
@@ -199,12 +212,16 @@ func (s *NativePluginSource) ExecuteCommand(command string, args []json.RawMessa
|
|
|
199
212
|
return nil, ErrCommandNotHandled
|
|
200
213
|
}
|
|
201
214
|
argsJSON, _ := json.Marshal(args)
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
"
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
215
|
+
cmdArgs := []string{
|
|
216
|
+
"--command=" + command,
|
|
217
|
+
"--arguments-json=" + string(argsJSON),
|
|
218
|
+
}
|
|
219
|
+
var stdin io.Reader
|
|
220
|
+
if hasContent {
|
|
221
|
+
cmdArgs = append(cmdArgs, "--content-stdin")
|
|
222
|
+
stdin = strings.NewReader(content)
|
|
223
|
+
}
|
|
224
|
+
body, err := s.runWithStdin(plugin, "lsp-execute-command", stdin, cmdArgs...)
|
|
208
225
|
if err != nil {
|
|
209
226
|
return nil, err
|
|
210
227
|
}
|
|
@@ -317,6 +334,14 @@ func (s *NativePluginSource) pluginOwnsCommand(plugin NativeLSPPluginEntry, comm
|
|
|
317
334
|
}
|
|
318
335
|
|
|
319
336
|
func (s *NativePluginSource) run(plugin NativeLSPPluginEntry, command string, args ...string) ([]byte, error) {
|
|
337
|
+
return s.runWithStdin(plugin, command, nil, args...)
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// runWithStdin runs a sidecar subcommand like run, additionally wiring stdin to
|
|
341
|
+
// the supplied reader when it is non-nil. Callers that do not pass buffer text
|
|
342
|
+
// (Diagnostics, CodeActions, discovery) reach this through run with a nil
|
|
343
|
+
// reader, leaving the sidecar's stdin unset exactly as before.
|
|
344
|
+
func (s *NativePluginSource) runWithStdin(plugin NativeLSPPluginEntry, command string, stdin io.Reader, args ...string) ([]byte, error) {
|
|
320
345
|
if strings.TrimSpace(plugin.Binary) == "" {
|
|
321
346
|
return nil, fmt.Errorf("ttscserver: %s has no binary", pluginLabel(plugin))
|
|
322
347
|
}
|
|
@@ -332,6 +357,9 @@ func (s *NativePluginSource) run(plugin NativeLSPPluginEntry, command string, ar
|
|
|
332
357
|
cmd := exec.CommandContext(ctx, plugin.Binary, allArgs...)
|
|
333
358
|
cmd.Dir = s.cwd
|
|
334
359
|
cmd.Env = os.Environ()
|
|
360
|
+
if stdin != nil {
|
|
361
|
+
cmd.Stdin = stdin
|
|
362
|
+
}
|
|
335
363
|
stdout := limitedBuffer{limit: nativePluginCommandStdoutLimit}
|
|
336
364
|
stderr := limitedBuffer{limit: nativePluginCommandStderrLimit}
|
|
337
365
|
cmd.Stdout = &stdout
|
|
@@ -31,8 +31,15 @@ const (
|
|
|
31
31
|
methodCodeAction = "textDocument/codeAction"
|
|
32
32
|
methodExecuteCommand = "workspace/executeCommand"
|
|
33
33
|
methodCancelRequest = "$/cancelRequest"
|
|
34
|
+
methodFormatting = "textDocument/formatting"
|
|
34
35
|
)
|
|
35
36
|
|
|
37
|
+
// formatDocumentCommand is the ttsc-owned workspace command that the lint
|
|
38
|
+
// sidecar advertises for whole-document formatting. The textDocument/formatting
|
|
39
|
+
// handler routes the cached editor buffer through this command so formatOnSave
|
|
40
|
+
// formats the live (possibly dirty) buffer rather than the on-disk file.
|
|
41
|
+
const formatDocumentCommand = "ttsc.format.document"
|
|
42
|
+
|
|
36
43
|
// ProxyOptions wires the byte-level proxy together. ttscserver creates
|
|
37
44
|
// the upstream pipes around `tsgo --lsp --stdio` and hands the proxy
|
|
38
45
|
// editor stdio plus those pipe ends.
|
|
@@ -88,6 +95,15 @@ type Proxy struct {
|
|
|
88
95
|
documentGeneration map[string]uint64
|
|
89
96
|
dirtyDocuments map[string]struct{}
|
|
90
97
|
dirtyVersions map[string]*int
|
|
98
|
+
// documentText caches the live editor buffer per uri so the
|
|
99
|
+
// textDocument/formatting handler can format the in-memory text instead
|
|
100
|
+
// of the on-disk file. Entries are present only while the proxy holds a
|
|
101
|
+
// trustworthy full-document copy: didOpen / full-sync didChange populate
|
|
102
|
+
// it, incremental didChange (a contentChange carrying a range) and
|
|
103
|
+
// didClose evict it, and the formatting handler falls back to a disk read
|
|
104
|
+
// when no entry is cached. Guarded by diagnosticsMu like the other
|
|
105
|
+
// per-uri document state.
|
|
106
|
+
documentText map[string]string
|
|
91
107
|
}
|
|
92
108
|
|
|
93
109
|
type pendingCodeActionRequest struct {
|
|
@@ -137,6 +153,7 @@ func NewProxy(opts ProxyOptions) *Proxy {
|
|
|
137
153
|
documentGeneration: make(map[string]uint64),
|
|
138
154
|
dirtyDocuments: make(map[string]struct{}),
|
|
139
155
|
dirtyVersions: make(map[string]*int),
|
|
156
|
+
documentText: make(map[string]string),
|
|
140
157
|
}
|
|
141
158
|
}
|
|
142
159
|
|
|
@@ -232,6 +249,7 @@ func (p *Proxy) handleEditorEnvelope(env Envelope, body []byte) (bool, error) {
|
|
|
232
249
|
}
|
|
233
250
|
case methodDidOpen:
|
|
234
251
|
if env.IsNotification() {
|
|
252
|
+
p.cacheDidOpenText(env)
|
|
235
253
|
if err := p.publishPluginDiagnosticsForDidOpen(env); err != nil {
|
|
236
254
|
return false, err
|
|
237
255
|
}
|
|
@@ -242,18 +260,24 @@ func (p *Proxy) handleEditorEnvelope(env Envelope, body []byte) (bool, error) {
|
|
|
242
260
|
}
|
|
243
261
|
case methodDidChange:
|
|
244
262
|
if env.IsNotification() {
|
|
263
|
+
p.cacheDidChangeText(env)
|
|
245
264
|
if err := p.markDocumentDirty(env); err != nil {
|
|
246
265
|
return false, err
|
|
247
266
|
}
|
|
248
267
|
}
|
|
249
268
|
case methodDidClose:
|
|
250
269
|
if env.IsNotification() {
|
|
270
|
+
p.evictDocumentText(env)
|
|
251
271
|
p.clearDocumentDiagnostics(env)
|
|
252
272
|
}
|
|
253
273
|
case methodExecuteCommand:
|
|
254
274
|
if env.IsRequest() {
|
|
255
275
|
return p.tryExecuteCommand(env)
|
|
256
276
|
}
|
|
277
|
+
case methodFormatting:
|
|
278
|
+
if env.IsRequest() {
|
|
279
|
+
return p.handleFormattingRequest(env)
|
|
280
|
+
}
|
|
257
281
|
case methodCodeAction:
|
|
258
282
|
if env.IsRequest() {
|
|
259
283
|
return p.handleCodeActionRequest(env)
|
|
@@ -433,7 +457,7 @@ func (p *Proxy) pluginOnlyCodeActionKinds() map[string]struct{} {
|
|
|
433
457
|
kinds := map[string]struct{}{
|
|
434
458
|
"source.fixAll.ttsc": {},
|
|
435
459
|
}
|
|
436
|
-
if p.ownsCommand(
|
|
460
|
+
if p.ownsCommand(formatDocumentCommand) {
|
|
437
461
|
kinds["source.format"] = struct{}{}
|
|
438
462
|
}
|
|
439
463
|
return kinds
|
|
@@ -493,6 +517,102 @@ func (p *Proxy) completeExecuteCommand(env Envelope, key string, command string,
|
|
|
493
517
|
p.reportAsyncError(p.writeExecuteCommandResultIfClean(env.ID, pending, edit))
|
|
494
518
|
}
|
|
495
519
|
|
|
520
|
+
// contentExecutor is the optional capability a PluginSource exposes to format an
|
|
521
|
+
// in-memory buffer instead of the on-disk file. NativePluginSource implements it
|
|
522
|
+
// by piping the buffer to the sidecar's stdin with --content-stdin; sources that
|
|
523
|
+
// do not implement it fall back to plain ExecuteCommand (disk).
|
|
524
|
+
type contentExecutor interface {
|
|
525
|
+
ExecuteCommandWithContent(command string, args []json.RawMessage, content string, hasContent bool) (*LSPWorkspaceEdit, error)
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// handleFormattingRequest answers textDocument/formatting for the ttsc-owned
|
|
529
|
+
// document formatter. Unlike the workspace/executeCommand path, this handler
|
|
530
|
+
// intentionally formats the live (possibly dirty) editor buffer: it reads the
|
|
531
|
+
// cached buffer text and passes it to the sidecar so formatOnSave works before
|
|
532
|
+
// the file is written to disk. It therefore does NOT go through the
|
|
533
|
+
// dirty-document guard that writeExecuteCommandResultIfClean applies to the
|
|
534
|
+
// executeCommand path.
|
|
535
|
+
//
|
|
536
|
+
// When ttsc does not own ttsc.format.document the request is forwarded to
|
|
537
|
+
// upstream tsgo (return false). The handler never surfaces an error to the
|
|
538
|
+
// editor: on any failure it replies with an empty TextEdit array so a failed
|
|
539
|
+
// formatter cannot break the save.
|
|
540
|
+
func (p *Proxy) handleFormattingRequest(env Envelope) (bool, error) {
|
|
541
|
+
if !p.ownsCommand(formatDocumentCommand) {
|
|
542
|
+
return false, nil
|
|
543
|
+
}
|
|
544
|
+
var params struct {
|
|
545
|
+
TextDocument struct {
|
|
546
|
+
URI string `json:"uri"`
|
|
547
|
+
} `json:"textDocument"`
|
|
548
|
+
}
|
|
549
|
+
if err := json.Unmarshal(env.Params, ¶ms); err != nil || params.TextDocument.URI == "" {
|
|
550
|
+
return false, nil
|
|
551
|
+
}
|
|
552
|
+
uri := params.TextDocument.URI
|
|
553
|
+
go p.completeFormattingRequest(env, uri)
|
|
554
|
+
return true, nil
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
func (p *Proxy) completeFormattingRequest(env Envelope, uri string) {
|
|
558
|
+
// hasContent distinguishes "the proxy has a buffer to format in-memory" from
|
|
559
|
+
// "no buffer; let the sidecar read disk". An empty cached buffer is a valid
|
|
560
|
+
// document state (the user cleared the file), so the empty string must NOT be
|
|
561
|
+
// overloaded as the no-buffer sentinel: a cache hit always yields
|
|
562
|
+
// hasContent=true even when the buffer is "".
|
|
563
|
+
content, hasContent := p.cachedDocumentText(uri)
|
|
564
|
+
if !hasContent {
|
|
565
|
+
if file, fileOK := filePathFromURI(uri); fileOK {
|
|
566
|
+
if disk, err := os.ReadFile(file); err == nil {
|
|
567
|
+
// Preserve the existing disk-fallback behavior: pipe the disk bytes
|
|
568
|
+
// via stdin so the sidecar formats exactly what the proxy read.
|
|
569
|
+
content = string(disk)
|
|
570
|
+
hasContent = true
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
// A formatter failure must never break the editor's save: reply with an
|
|
575
|
+
// empty TextEdit[] regardless of the error. The NativePluginSource already
|
|
576
|
+
// logs the underlying sidecar failure to its own stderr writer, so the proxy
|
|
577
|
+
// does not need a separate log sink here.
|
|
578
|
+
edit, err := p.executeFormatCommand(uri, content, hasContent)
|
|
579
|
+
if err != nil {
|
|
580
|
+
p.reportAsyncError(p.writeResult(env.ID, []LSPTextEdit{}))
|
|
581
|
+
return
|
|
582
|
+
}
|
|
583
|
+
edits := formattingTextEdits(edit, uri)
|
|
584
|
+
p.reportAsyncError(p.writeResult(env.ID, edits))
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// executeFormatCommand runs ttsc.format.document against the supplied buffer
|
|
588
|
+
// text. The single command argument is the document uri, matching the
|
|
589
|
+
// executeCommand path the sidecar already implements; --content-stdin makes the
|
|
590
|
+
// sidecar format the piped text instead of the disk file when the source
|
|
591
|
+
// supports it.
|
|
592
|
+
func (p *Proxy) executeFormatCommand(uri string, content string, hasContent bool) (*LSPWorkspaceEdit, error) {
|
|
593
|
+
arg, _ := json.Marshal(uri)
|
|
594
|
+
args := []json.RawMessage{arg}
|
|
595
|
+
if executor, ok := p.source.(contentExecutor); ok {
|
|
596
|
+
return executor.ExecuteCommandWithContent(formatDocumentCommand, args, content, hasContent)
|
|
597
|
+
}
|
|
598
|
+
return p.source.ExecuteCommand(formatDocumentCommand, args)
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// formattingTextEdits projects a WorkspaceEdit returned by the formatter onto
|
|
602
|
+
// the LSP textDocument/formatting response shape: the array of TextEdits that
|
|
603
|
+
// target uri. A nil edit or a no-op (no changes for uri) yields an empty,
|
|
604
|
+
// non-nil slice so the editor always receives a valid TextEdit[].
|
|
605
|
+
func formattingTextEdits(edit *LSPWorkspaceEdit, uri string) []LSPTextEdit {
|
|
606
|
+
if edit == nil {
|
|
607
|
+
return []LSPTextEdit{}
|
|
608
|
+
}
|
|
609
|
+
edits, ok := edit.Changes[uri]
|
|
610
|
+
if !ok || edits == nil {
|
|
611
|
+
return []LSPTextEdit{}
|
|
612
|
+
}
|
|
613
|
+
return edits
|
|
614
|
+
}
|
|
615
|
+
|
|
496
616
|
// ownsCommand reports whether command is registered with the PluginSource
|
|
497
617
|
// and should be handled locally rather than forwarded to upstream tsgo.
|
|
498
618
|
func (p *Proxy) ownsCommand(command string) bool {
|
|
@@ -928,6 +1048,89 @@ func (p *Proxy) clearDocumentDiagnostics(env Envelope) {
|
|
|
928
1048
|
p.documentGeneration[params.TextDocument.URI]++
|
|
929
1049
|
}
|
|
930
1050
|
|
|
1051
|
+
// cacheDidOpenText stores the buffer text the editor opened so the
|
|
1052
|
+
// formatting handler can format the live document. didOpen always carries the
|
|
1053
|
+
// full text, so the cache is unconditionally trustworthy here.
|
|
1054
|
+
func (p *Proxy) cacheDidOpenText(env Envelope) {
|
|
1055
|
+
var params struct {
|
|
1056
|
+
TextDocument struct {
|
|
1057
|
+
URI string `json:"uri"`
|
|
1058
|
+
Text string `json:"text"`
|
|
1059
|
+
} `json:"textDocument"`
|
|
1060
|
+
}
|
|
1061
|
+
if err := json.Unmarshal(env.Params, ¶ms); err != nil || params.TextDocument.URI == "" {
|
|
1062
|
+
return
|
|
1063
|
+
}
|
|
1064
|
+
p.diagnosticsMu.Lock()
|
|
1065
|
+
defer p.diagnosticsMu.Unlock()
|
|
1066
|
+
p.documentText[params.TextDocument.URI] = params.TextDocument.Text
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
// cacheDidChangeText refreshes the buffer cache from a didChange notification.
|
|
1070
|
+
// LSP delivers contentChanges either as full-document replacements (no range)
|
|
1071
|
+
// or incremental range edits (a range present). The proxy does not control the
|
|
1072
|
+
// advertised textDocumentSync kind — tsgo owns the initialize response and ttsc
|
|
1073
|
+
// only augments code-action/executeCommand capabilities — so a client may send
|
|
1074
|
+
// either shape. The cache is only trustworthy when every change is a full
|
|
1075
|
+
// replacement; on an incremental change the proxy evicts the entry so the
|
|
1076
|
+
// formatting handler falls back to reading disk rather than formatting stale
|
|
1077
|
+
// text.
|
|
1078
|
+
func (p *Proxy) cacheDidChangeText(env Envelope) {
|
|
1079
|
+
var params struct {
|
|
1080
|
+
TextDocument struct {
|
|
1081
|
+
URI string `json:"uri"`
|
|
1082
|
+
} `json:"textDocument"`
|
|
1083
|
+
ContentChanges []struct {
|
|
1084
|
+
Range *json.RawMessage `json:"range"`
|
|
1085
|
+
Text string `json:"text"`
|
|
1086
|
+
} `json:"contentChanges"`
|
|
1087
|
+
}
|
|
1088
|
+
if err := json.Unmarshal(env.Params, ¶ms); err != nil || params.TextDocument.URI == "" {
|
|
1089
|
+
return
|
|
1090
|
+
}
|
|
1091
|
+
if len(params.ContentChanges) == 0 {
|
|
1092
|
+
return
|
|
1093
|
+
}
|
|
1094
|
+
fullText := ""
|
|
1095
|
+
for _, change := range params.ContentChanges {
|
|
1096
|
+
if change.Range != nil {
|
|
1097
|
+
p.diagnosticsMu.Lock()
|
|
1098
|
+
delete(p.documentText, params.TextDocument.URI)
|
|
1099
|
+
p.diagnosticsMu.Unlock()
|
|
1100
|
+
return
|
|
1101
|
+
}
|
|
1102
|
+
fullText = change.Text
|
|
1103
|
+
}
|
|
1104
|
+
p.diagnosticsMu.Lock()
|
|
1105
|
+
defer p.diagnosticsMu.Unlock()
|
|
1106
|
+
p.documentText[params.TextDocument.URI] = fullText
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
// evictDocumentText drops the cached buffer for a closed document so a later
|
|
1110
|
+
// reopen does not format against the previous session's text.
|
|
1111
|
+
func (p *Proxy) evictDocumentText(env Envelope) {
|
|
1112
|
+
var params struct {
|
|
1113
|
+
TextDocument struct {
|
|
1114
|
+
URI string `json:"uri"`
|
|
1115
|
+
} `json:"textDocument"`
|
|
1116
|
+
}
|
|
1117
|
+
if err := json.Unmarshal(env.Params, ¶ms); err != nil || params.TextDocument.URI == "" {
|
|
1118
|
+
return
|
|
1119
|
+
}
|
|
1120
|
+
p.diagnosticsMu.Lock()
|
|
1121
|
+
defer p.diagnosticsMu.Unlock()
|
|
1122
|
+
delete(p.documentText, params.TextDocument.URI)
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
// cachedDocumentText returns the cached buffer text for uri and whether an
|
|
1126
|
+
// entry was present.
|
|
1127
|
+
func (p *Proxy) cachedDocumentText(uri string) (string, bool) {
|
|
1128
|
+
p.diagnosticsMu.Lock()
|
|
1129
|
+
defer p.diagnosticsMu.Unlock()
|
|
1130
|
+
text, ok := p.documentText[uri]
|
|
1131
|
+
return text, ok
|
|
1132
|
+
}
|
|
1133
|
+
|
|
931
1134
|
func (p *Proxy) markDocumentDirty(env Envelope) error {
|
|
932
1135
|
var params struct {
|
|
933
1136
|
TextDocument struct {
|
|
@@ -1176,6 +1379,17 @@ func (p *Proxy) augmentInitializeResult(env Envelope) ([]byte, bool) {
|
|
|
1176
1379
|
caps["executeCommandProvider"] = provider
|
|
1177
1380
|
changed = true
|
|
1178
1381
|
}
|
|
1382
|
+
// Advertise documentFormattingProvider when ttsc owns the document
|
|
1383
|
+
// formatter so editors send textDocument/formatting (formatOnSave). The
|
|
1384
|
+
// proxy intercepts that method and formats the live buffer, so it forces
|
|
1385
|
+
// the capability on even if upstream tsgo already advertised one — tsgo's
|
|
1386
|
+
// formatter would otherwise format the on-disk file and lose unsaved edits.
|
|
1387
|
+
if p.ownsCommand(formatDocumentCommand) {
|
|
1388
|
+
if existing, ok := caps["documentFormattingProvider"].(bool); !ok || !existing {
|
|
1389
|
+
caps["documentFormattingProvider"] = true
|
|
1390
|
+
changed = true
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1179
1393
|
if !changed {
|
|
1180
1394
|
return nil, false
|
|
1181
1395
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ttsc",
|
|
3
|
-
"version": "0.14.0-dev.
|
|
3
|
+
"version": "0.14.0-dev.20260529.2",
|
|
4
4
|
"description": "General-purpose TypeScript-Go compiler, runtime, plugin host, and LSP host.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -32,13 +32,13 @@
|
|
|
32
32
|
"rimraf": "^6.1.2"
|
|
33
33
|
},
|
|
34
34
|
"optionalDependencies": {
|
|
35
|
-
"@ttsc/linux-x64": "0.14.0-dev.
|
|
36
|
-
"@ttsc/linux-arm": "0.14.0-dev.
|
|
37
|
-
"@ttsc/linux-arm64": "0.14.0-dev.
|
|
38
|
-
"@ttsc/darwin-x64": "0.14.0-dev.
|
|
39
|
-
"@ttsc/darwin-arm64": "0.14.0-dev.
|
|
40
|
-
"@ttsc/win32-x64": "0.14.0-dev.
|
|
41
|
-
"@ttsc/win32-arm64": "0.14.0-dev.
|
|
35
|
+
"@ttsc/linux-x64": "0.14.0-dev.20260529.2",
|
|
36
|
+
"@ttsc/linux-arm": "0.14.0-dev.20260529.2",
|
|
37
|
+
"@ttsc/linux-arm64": "0.14.0-dev.20260529.2",
|
|
38
|
+
"@ttsc/darwin-x64": "0.14.0-dev.20260529.2",
|
|
39
|
+
"@ttsc/darwin-arm64": "0.14.0-dev.20260529.2",
|
|
40
|
+
"@ttsc/win32-x64": "0.14.0-dev.20260529.2",
|
|
41
|
+
"@ttsc/win32-arm64": "0.14.0-dev.20260529.2"
|
|
42
42
|
},
|
|
43
43
|
"sideEffects": false,
|
|
44
44
|
"files": [
|