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.
Files changed (62) hide show
  1. package/README.md +36 -3
  2. package/cmd/ttscserver/main.go +56 -14
  3. package/cmd/ttscserver/run_lsp_forwards_execute_command_advertisement_flags_test.go +55 -0
  4. package/cmd/ttscserver/run_lsp_uses_native_plugin_manifest_source_test.go +45 -0
  5. package/driver/lsp.go +21 -1
  6. package/internal/lspserver/lsp_frame.go +30 -10
  7. package/internal/lspserver/lsp_native_plugin_source.go +380 -0
  8. package/internal/lspserver/lsp_plugin.go +16 -11
  9. package/internal/lspserver/lsp_proxy.go +1153 -73
  10. package/internal/lspserver/lsp_server.go +17 -5
  11. package/lib/TtscCompiler.d.ts +5 -4
  12. package/lib/TtscCompiler.js +56 -4
  13. package/lib/TtscCompiler.js.map +1 -1
  14. package/lib/compiler/internal/runBuild.js +149 -38
  15. package/lib/compiler/internal/runBuild.js.map +1 -1
  16. package/lib/compiler/internal/transformProjectInMemory.js +9 -7
  17. package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
  18. package/lib/flags/schema.d.ts +42 -43
  19. package/lib/flags/schema.js +35 -17
  20. package/lib/flags/schema.js.map +1 -1
  21. package/lib/launcher/internal/getCompilerVersionText.js +6 -14
  22. package/lib/launcher/internal/getCompilerVersionText.js.map +1 -1
  23. package/lib/launcher/internal/prepareExecution.d.ts +1 -0
  24. package/lib/launcher/internal/prepareExecution.js +31 -16
  25. package/lib/launcher/internal/prepareExecution.js.map +1 -1
  26. package/lib/launcher/internal/runTtscserver.d.ts +3 -1
  27. package/lib/launcher/internal/runTtscserver.js +88 -11
  28. package/lib/launcher/internal/runTtscserver.js.map +1 -1
  29. package/lib/launcher/internal/runTtsx.js +39 -26
  30. package/lib/launcher/internal/runTtsx.js.map +1 -1
  31. package/lib/structures/ITtscCompilerContext.d.ts +1 -1
  32. package/lib/structures/ITtscCompilerResult.d.ts +11 -0
  33. package/lib/structures/ITtscCompilerTransformation.d.ts +14 -3
  34. package/lib/structures/ITtscPlugin.d.ts +24 -15
  35. package/lib/structures/ITtscPluginCapabilities.d.ts +36 -14
  36. package/lib/structures/ITtscPluginContributor.d.ts +4 -2
  37. package/lib/structures/ITtscPluginFactoryContext.d.ts +3 -3
  38. package/lib/structures/ITtscProjectPluginConfig.d.ts +3 -3
  39. package/lib/structures/TtscPluginStage.d.ts +4 -3
  40. package/lib/structures/internal/ITtscLoadedNativePlugin.d.ts +7 -4
  41. package/package.json +10 -10
  42. package/shim/ast/lint.go +7 -1
  43. package/shim/checker/shim.go +3 -0
  44. package/shim/core/shim.go +11 -0
  45. package/src/TtscCompiler.ts +67 -4
  46. package/src/compiler/internal/runBuild.ts +209 -33
  47. package/src/compiler/internal/transformProjectInMemory.ts +10 -5
  48. package/src/flags/schema.ts +62 -43
  49. package/src/launcher/internal/getCompilerVersionText.ts +4 -13
  50. package/src/launcher/internal/prepareExecution.ts +31 -16
  51. package/src/launcher/internal/runTtscserver.ts +105 -11
  52. package/src/launcher/internal/runTtsx.ts +41 -29
  53. package/src/structures/ITtscCompilerContext.ts +1 -1
  54. package/src/structures/ITtscCompilerResult.ts +12 -0
  55. package/src/structures/ITtscCompilerTransformation.ts +15 -3
  56. package/src/structures/ITtscPlugin.ts +24 -15
  57. package/src/structures/ITtscPluginCapabilities.ts +38 -14
  58. package/src/structures/ITtscPluginContributor.ts +4 -2
  59. package/src/structures/ITtscPluginFactoryContext.ts +3 -3
  60. package/src/structures/ITtscProjectPluginConfig.ts +3 -3
  61. package/src/structures/TtscPluginStage.ts +4 -3
  62. package/src/structures/internal/ITtscLoadedNativePlugin.ts +7 -4
package/README.md CHANGED
@@ -11,6 +11,9 @@
11
11
 
12
12
  A `typescript-go` toolchain for compiler-powered plugins and type-safe execution.
13
13
 
14
+ Benchmarked on the VS Code fixture: up to **10x** faster type checks and a
15
+ **500x** faster measured lint pass.
16
+
14
17
  - **`ttsc`**: build, check, and transform.
15
18
  - **`ttsx`**: execute TypeScript with type checking.
16
19
  - native TypeScript-Go execution instead of transpile-only runners.
@@ -54,7 +57,8 @@ npx ttsc format
54
57
 
55
58
  ### Editor
56
59
 
57
- Install the VS Code extension to see lint and plugin diagnostics live as you type:
60
+ Install the VS Code extension for live TypeScript-Go editor features plus
61
+ saved-state ttsc plugin diagnostics and actions:
58
62
 
59
63
  ```bash
60
64
  npm install -D @ttsc/vscode
@@ -140,11 +144,12 @@ interface IMember {
140
144
  The transform replaces `typia.is<IMember>()` with dedicated JavaScript checks at build time:
141
145
 
142
146
  ```js
143
- import * as __typia_transform__isFormatUuid from "typia/lib/internal/_isFormatUuid";
147
+ import typia from "typia";
144
148
  import * as __typia_transform__isFormatEmail from "typia/lib/internal/_isFormatEmail";
149
+ import * as __typia_transform__isFormatUuid from "typia/lib/internal/_isFormatUuid";
145
150
  import * as __typia_transform__isTypeUint32 from "typia/lib/internal/_isTypeUint32";
146
- import typia from "typia";
147
151
  import { v4 } from "uuid";
152
+
148
153
  const matched = (() => {
149
154
  const _io0 = (input) =>
150
155
  "string" === typeof input.id &&
@@ -164,6 +169,34 @@ const matched = (() => {
164
169
  console.log(matched); // true
165
170
  ```
166
171
 
172
+ ## Programmatic API
173
+
174
+ Embed `ttsc` from another Node tool with the `TtscCompiler` class:
175
+
176
+ ```ts
177
+ import { TtscCompiler } from "ttsc";
178
+
179
+ const compiler = new TtscCompiler({ cwd: "./project" });
180
+ const result = compiler.compile();
181
+
182
+ if (result.type === "success") {
183
+ for (const [path, text] of Object.entries(result.output)) {
184
+ // path is project-relative ("dist/index.js", "dist/index.d.ts", ...)
185
+ console.log(path, text.length);
186
+ }
187
+ } else if (result.type === "failure") {
188
+ for (const d of result.diagnostics) {
189
+ console.error(`${d.file}:${d.line}:${d.character} ${d.messageText}`);
190
+ }
191
+ }
192
+ ```
193
+
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.
199
+
167
200
  ### List of Plugins
168
201
 
169
202
  `ttsc` ships a few small utility plugins in this repository.
@@ -1,7 +1,8 @@
1
1
  // Command ttscserver is the Go LSP host shipped by ttsc. It wraps the
2
2
  // project-selected TypeScript-Go LSP server process and proxies traffic
3
- // between the editor and that server, splicing ttsc-plugin diagnostics,
4
- // code actions, and ttsc-owned executeCommand handling into the same stream.
3
+ // between the editor and that server. The JavaScript launcher resolves project
4
+ // plugins first and passes an LSP manifest so this command can merge plugin
5
+ // diagnostics, code actions, and ttsc-owned executeCommand handling.
5
6
  //
6
7
  // The JavaScript launcher (`packages/ttsc/src/launcher/ttscserver.ts`)
7
8
  // resolves the native binary and forwards stdio so editors can spawn
@@ -86,8 +87,13 @@ func runLSP(args []string) int {
86
87
  fs.SetOutput(stderr)
87
88
  stdioFlag := fs.Bool("stdio", false, "communicate with the editor over stdin/stdout")
88
89
  cwdFlag := fs.String("cwd", "", "project root (defaults to process cwd)")
90
+ tsconfigFlag := fs.String("tsconfig", "tsconfig.json", "project tsconfig path")
89
91
  tsgoFlag := fs.String("tsgo", "", "absolute tsgo binary path (defaults to TTSC_TSGO_BINARY)")
90
- progressDelayFlag := fs.Duration("progress-delay", 250*time.Millisecond, "delay before showing tsgo's progress UI")
92
+ progressDelayFlag := fs.Duration("progress-delay", 250*time.Millisecond, "accepted for compatibility; ignored by the external tsgo LSP process")
93
+ suppressExecuteCommandProviderFlag := fs.Bool("suppress-execute-command-provider", false, "do not advertise ttsc executeCommand ids during initialize")
94
+ suppressExecuteCommandIDsFlag := fs.String("suppress-execute-command-ids", "", "comma-separated ttsc executeCommand ids to omit during initialize")
95
+ executeCommandIDPrefixFlag := fs.String("execute-command-id-prefix", "", "prefix to apply to advertised executeCommand ids")
96
+ _ = fs.String("clientProcessId", "", "ignored VSCode language-client compatibility flag")
91
97
  if err := fs.Parse(args); err != nil {
92
98
  return 2
93
99
  }
@@ -114,14 +120,28 @@ func runLSP(args []string) int {
114
120
  tsgoBinary = strings.TrimSpace(os.Getenv("TTSC_TSGO_BINARY"))
115
121
  }
116
122
 
117
- err := runLSPServer(ctx, lspserver.LSPServerOptions{
118
- In: stdin,
119
- Out: stdout,
120
- Err: stderr,
121
- Cwd: cwd,
122
- TsgoBinary: tsgoBinary,
123
- Source: lspserver.NullPluginSource{},
124
- ProgressDelay: *progressDelayFlag,
123
+ source, err := lspserver.NewNativePluginSource(lspserver.NativePluginSourceOptions{
124
+ Cwd: cwd,
125
+ Err: stderr,
126
+ ManifestJSON: os.Getenv("TTSC_LSP_PLUGINS_JSON"),
127
+ Tsconfig: strings.TrimSpace(*tsconfigFlag),
128
+ })
129
+ if err != nil {
130
+ fmt.Fprintf(stderr, "ttscserver: %v\n", err)
131
+ return 2
132
+ }
133
+
134
+ err = runLSPServer(ctx, lspserver.LSPServerOptions{
135
+ In: stdin,
136
+ Out: stdout,
137
+ Err: stderr,
138
+ Cwd: cwd,
139
+ TsgoBinary: tsgoBinary,
140
+ Source: source,
141
+ SuppressExecuteCommandProvider: *suppressExecuteCommandProviderFlag,
142
+ SuppressedExecuteCommandIDs: splitCSV(*suppressExecuteCommandIDsFlag),
143
+ ExecuteCommandIDPrefix: strings.TrimSpace(*executeCommandIDPrefixFlag),
144
+ ProgressDelay: *progressDelayFlag,
125
145
  })
126
146
  if err != nil && !errors.Is(err, context.Canceled) {
127
147
  fmt.Fprintf(stderr, "ttscserver: %v\n", err)
@@ -155,14 +175,36 @@ Usage:
155
175
  Options:
156
176
  --stdio Communicate with the editor over stdin/stdout.
157
177
  --cwd <dir> Project root used as the tsgo server working directory.
178
+ --tsconfig <path> Project config path used by ttsc plugin sidecars.
158
179
  --tsgo <path> Absolute tsgo binary path (defaults to TTSC_TSGO_BINARY).
159
- --progress-delay D Accepted for compatibility; tsgo currently owns this value.
180
+ --suppress-execute-command-provider
181
+ Do not advertise ttsc executeCommand ids during initialize.
182
+ --suppress-execute-command-ids <ids>
183
+ Comma-separated executeCommand ids to omit during initialize.
184
+ --execute-command-id-prefix <prefix>
185
+ Prefix advertised executeCommand ids for multi-client hosts.
186
+ --progress-delay D Accepted for compatibility; currently ignored by the external tsgo LSP process.
160
187
 
161
188
  Typical embedding:
162
189
  Editors spawn ttscserver via the JavaScript launcher (resolves the
163
190
  per-platform native binary and passes the project tsgo path) and exchange
164
191
  LSP messages over stdio. The upstream tsgo server provides hover,
165
- completion, definitions, and diagnostics; ttsc merges plugin diagnostics,
166
- code actions, and ttsc-owned executeCommand handlers into the same stream.
192
+ completion, definitions, and diagnostics. LSP-capable ttsc sidecars are
193
+ discovered by the JavaScript launcher and merged into the same stream.
167
194
  `))
168
195
  }
196
+
197
+ func splitCSV(value string) []string {
198
+ if value == "" {
199
+ return nil
200
+ }
201
+ fields := strings.Split(value, ",")
202
+ out := make([]string, 0, len(fields))
203
+ for _, field := range fields {
204
+ trimmed := strings.TrimSpace(field)
205
+ if trimmed != "" {
206
+ out = append(out, trimmed)
207
+ }
208
+ }
209
+ return out
210
+ }
@@ -0,0 +1,55 @@
1
+ package main
2
+
3
+ import (
4
+ "bytes"
5
+ "context"
6
+ "reflect"
7
+ "testing"
8
+
9
+ "github.com/samchon/ttsc/packages/ttsc/internal/lspserver"
10
+ )
11
+
12
+ // TestRunLSPForwardsExecuteCommandAdvertisementFlags verifies the native
13
+ // command passes executeCommand registration controls into the LSP host.
14
+ //
15
+ // The VS Code extension relies on these flags to suppress wrapper-owned command
16
+ // ids and namespace custom plugin commands per project root. Parsing the flags
17
+ // in the command is not enough; this pins the handoff into LSPServerOptions.
18
+ //
19
+ // 1. Substitute the runLSPServer seam and capture its options.
20
+ // 2. Run runLSP with suppression and prefix flags.
21
+ // 3. Assert the captured LSPServerOptions carry the parsed values.
22
+ func TestRunLSPForwardsExecuteCommandAdvertisementFlags(t *testing.T) {
23
+ prev := runLSPServer
24
+ var captured lspserver.LSPServerOptions
25
+ runLSPServer = func(_ context.Context, opts lspserver.LSPServerOptions) error {
26
+ captured = opts
27
+ return nil
28
+ }
29
+ defer func() { runLSPServer = prev }()
30
+
31
+ outBuf := &bytes.Buffer{}
32
+ errBuf := &bytes.Buffer{}
33
+ withIO(t, outBuf, errBuf, nil, func() {
34
+ if code := runLSP([]string{
35
+ "--stdio",
36
+ "--cwd", t.TempDir(),
37
+ "--suppress-execute-command-provider",
38
+ "--suppress-execute-command-ids", " ttsc.lint.fixAll, ttsc.format.document ,, ",
39
+ "--execute-command-id-prefix", " ttsc.vscode.root. ",
40
+ }); code != 0 {
41
+ t.Fatalf("expected exit 0, got %d (stderr=%q)", code, errBuf.String())
42
+ }
43
+ })
44
+
45
+ if !captured.SuppressExecuteCommandProvider {
46
+ t.Fatal("expected SuppressExecuteCommandProvider to be true")
47
+ }
48
+ expectedIDs := []string{"ttsc.lint.fixAll", "ttsc.format.document"}
49
+ if !reflect.DeepEqual(captured.SuppressedExecuteCommandIDs, expectedIDs) {
50
+ t.Fatalf("expected suppressed ids %#v, got %#v", expectedIDs, captured.SuppressedExecuteCommandIDs)
51
+ }
52
+ if captured.ExecuteCommandIDPrefix != "ttsc.vscode.root." {
53
+ t.Fatalf("expected trimmed execute command prefix, got %q", captured.ExecuteCommandIDPrefix)
54
+ }
55
+ }
@@ -0,0 +1,45 @@
1
+ package main
2
+
3
+ import (
4
+ "bytes"
5
+ "context"
6
+ "testing"
7
+
8
+ "github.com/samchon/ttsc/packages/ttsc/internal/lspserver"
9
+ )
10
+
11
+ // TestRunLSPUsesNativePluginManifestSource verifies the command wires the
12
+ // launcher-provided plugin manifest into the proxy source.
13
+ //
14
+ // A previous implementation always passed NullPluginSource, so editor sessions
15
+ // could never receive ttsc plugin diagnostics or commands. This test pins the
16
+ // command-level seam without starting tsgo: the JavaScript launcher owns plugin
17
+ // discovery, and the Go command must turn its manifest into a NativePluginSource.
18
+ //
19
+ // 1. Set TTSC_LSP_PLUGINS_JSON to an empty but valid manifest.
20
+ // 2. Substitute runLSPServer and capture its LSPServerOptions.
21
+ // 3. Run `ttscserver --stdio` against a temp cwd.
22
+ // 4. Assert Source is the native sidecar-backed implementation.
23
+ func TestRunLSPUsesNativePluginManifestSource(t *testing.T) {
24
+ t.Setenv("TTSC_LSP_PLUGINS_JSON", `{"plugins":[],"lspPlugins":[]}`)
25
+
26
+ prev := runLSPServer
27
+ var captured lspserver.LSPServerOptions
28
+ runLSPServer = func(_ context.Context, opts lspserver.LSPServerOptions) error {
29
+ captured = opts
30
+ return nil
31
+ }
32
+ defer func() { runLSPServer = prev }()
33
+
34
+ outBuf := &bytes.Buffer{}
35
+ errBuf := &bytes.Buffer{}
36
+ withIO(t, outBuf, errBuf, nil, func() {
37
+ if code := runLSP([]string{"--stdio", "--cwd", t.TempDir(), "--tsconfig", "tsconfig.app.json"}); code != 0 {
38
+ t.Fatalf("expected exit 0, got %d (stderr=%q)", code, errBuf.String())
39
+ }
40
+ })
41
+
42
+ if _, ok := captured.Source.(*lspserver.NativePluginSource); !ok {
43
+ t.Fatalf("expected NativePluginSource, got %T", captured.Source)
44
+ }
45
+ }
package/driver/lsp.go CHANGED
@@ -1,5 +1,5 @@
1
1
  // lsp.go re-exports the public LSP surface from internal/lspserver so that
2
- // downstream consumers (plugins, the VSCode extension, tests) import only the
2
+ // downstream consumers (plugins, the VS Code extension, tests) import only the
3
3
  // driver package rather than reaching into the internal package directly. All
4
4
  // symbols are type aliases or var assignments so they are interchangeable with
5
5
  // the originals at the call site.
@@ -57,6 +57,21 @@ type PluginSource = lspserver.PluginSource
57
57
  // NullPluginSource is the driver-level alias for lspserver.NullPluginSource.
58
58
  type NullPluginSource = lspserver.NullPluginSource
59
59
 
60
+ // NativePluginManifest is the driver-level alias for lspserver.NativePluginManifest.
61
+ type NativePluginManifest = lspserver.NativePluginManifest
62
+
63
+ // NativePluginConfigEntry is the driver-level alias for lspserver.NativePluginConfigEntry.
64
+ type NativePluginConfigEntry = lspserver.NativePluginConfigEntry
65
+
66
+ // NativeLSPPluginEntry is the driver-level alias for lspserver.NativeLSPPluginEntry.
67
+ type NativeLSPPluginEntry = lspserver.NativeLSPPluginEntry
68
+
69
+ // NativePluginSourceOptions is the driver-level alias for lspserver.NativePluginSourceOptions.
70
+ type NativePluginSourceOptions = lspserver.NativePluginSourceOptions
71
+
72
+ // NativePluginSource is the driver-level alias for lspserver.NativePluginSource.
73
+ type NativePluginSource = lspserver.NativePluginSource
74
+
60
75
  // ProxyOptions is the driver-level alias for lspserver.ProxyOptions.
61
76
  type ProxyOptions = lspserver.ProxyOptions
62
77
 
@@ -79,6 +94,10 @@ type LSPUpstreamRunner = lspserver.LSPUpstreamRunner
79
94
  // the proxy will read without returning ErrFrameTooLarge.
80
95
  const MaxFrameBytes = lspserver.MaxFrameBytes
81
96
 
97
+ // MaxHeaderBytes is the maximum byte length of a JSON-RPC frame header block
98
+ // the proxy will read without returning ErrFrameTooLarge.
99
+ const MaxHeaderBytes = lspserver.MaxHeaderBytes
100
+
82
101
  // Sentinel errors forwarded from lspserver.
83
102
  var ErrCommandNotHandled = lspserver.ErrCommandNotHandled
84
103
  var ErrFrameClosed = lspserver.ErrFrameClosed
@@ -90,6 +109,7 @@ var ErrLSPTsgoBinaryRequired = lspserver.ErrLSPTsgoBinaryRequired
90
109
 
91
110
  // Constructor and utility functions forwarded from lspserver.
92
111
  var NewProxy = lspserver.NewProxy
112
+ var NewNativePluginSource = lspserver.NewNativePluginSource
93
113
  var NewFrameReader = lspserver.NewFrameReader
94
114
  var WriteFrame = lspserver.WriteFrame
95
115
  var ParseEnvelope = lspserver.ParseEnvelope
@@ -1,7 +1,8 @@
1
1
  // JSON-RPC Content-Length framing as used by LSP over stdio. ttscserver
2
2
  // wraps tsgo's LSP server but sits between it and the editor so it can
3
3
  // merge plugin diagnostics into outgoing publishDiagnostics and inject
4
- // ttsc-owned code actions. Both legs of the proxy work on raw bytes.
4
+ // ttsc-owned code actions and executeCommand results. Both legs of the
5
+ // proxy work on raw bytes.
5
6
  //
6
7
  // Headers are preserved when read so callers that inspect Content-Type
7
8
  // or vendor-specific headers can react to them; outgoing frames written
@@ -23,11 +24,11 @@ import (
23
24
  // shut down a pump loop without surfacing an error to the editor.
24
25
  var ErrFrameClosed = errors.New("lsp: frame stream closed")
25
26
 
26
- // ErrFrameTooLarge reports that a peer announced a Content-Length above
27
- // the safety cap. ttscserver bounds the value defensively so a confused
28
- // editor or compromised pipe cannot drive the proxy OOM with a single
29
- // gigabyte-scale header.
30
- var ErrFrameTooLarge = errors.New("lsp: frame body exceeds maximum size")
27
+ // ErrFrameTooLarge reports that a peer sent a header block or announced a
28
+ // Content-Length above the safety cap. ttscserver bounds both defensively so a
29
+ // confused editor or compromised pipe cannot drive the proxy OOM with a single
30
+ // gigabyte-scale frame.
31
+ var ErrFrameTooLarge = errors.New("lsp: frame exceeds maximum size")
31
32
 
32
33
  // MaxFrameBytes caps Content-Length to 64 MiB. The largest payload the
33
34
  // LSP base protocol routinely produces is workspace symbol responses
@@ -35,6 +36,11 @@ var ErrFrameTooLarge = errors.New("lsp: frame body exceeds maximum size")
35
36
  // editor traffic while still capping attacker-supplied lengths.
36
37
  const MaxFrameBytes = 64 << 20
37
38
 
39
+ // MaxHeaderBytes caps the LSP header block to 64 KiB. Real LSP traffic carries
40
+ // only Content-Length plus occasional Content-Type, so anything larger is a
41
+ // malformed peer rather than useful protocol data.
42
+ const MaxHeaderBytes = 64 << 10
43
+
38
44
  // FrameReader decodes Content-Length-framed JSON-RPC messages from r. It
39
45
  // is intentionally permissive about extra headers (we forward whatever the
40
46
  // peer sent) and strict about the Content-Length value because tsgo's
@@ -56,13 +62,27 @@ func (fr *FrameReader) Read() (headers string, body []byte, err error) {
56
62
  var headerBuf strings.Builder
57
63
  contentLength := -1
58
64
  for {
59
- line, lineErr := fr.br.ReadString('\n')
60
- if lineErr != nil {
61
- if lineErr == io.EOF && headerBuf.Len() == 0 && line == "" {
65
+ var lineBuf strings.Builder
66
+ for {
67
+ chunk, lineErr := fr.br.ReadSlice('\n')
68
+ if len(chunk) != 0 {
69
+ if headerBuf.Len()+lineBuf.Len()+len(chunk) > MaxHeaderBytes {
70
+ return "", nil, fmt.Errorf("%w (header got more than %d bytes)", ErrFrameTooLarge, MaxHeaderBytes)
71
+ }
72
+ lineBuf.Write(chunk)
73
+ }
74
+ if lineErr == nil {
75
+ break
76
+ }
77
+ if errors.Is(lineErr, bufio.ErrBufferFull) {
78
+ continue
79
+ }
80
+ if lineErr == io.EOF && headerBuf.Len() == 0 && lineBuf.Len() == 0 {
62
81
  return "", nil, ErrFrameClosed
63
82
  }
64
83
  return "", nil, fmt.Errorf("lsp: header read: %w", lineErr)
65
84
  }
85
+ line := lineBuf.String()
66
86
  trimmed := strings.TrimRight(line, "\r\n")
67
87
  if trimmed == "" {
68
88
  break
@@ -107,7 +127,7 @@ func parseContentLength(line string) (int, bool) {
107
127
 
108
128
  // WriteFrame serializes body with a Content-Length header to w. The header
109
129
  // uses CRLF line endings to match the LSP base protocol exactly so editors
110
- // that strict-parse the header block (notably VSCode's client) accept the
130
+ // that strict-parse the header block (notably VS Code's client) accept the
111
131
  // message without warnings.
112
132
  func WriteFrame(w io.Writer, body []byte) error {
113
133
  header := fmt.Sprintf("Content-Length: %d\r\n\r\n", len(body))