diff --git a/_extension/package.json b/_extension/package.json index baaac1a54b..d75ed38775 100644 --- a/_extension/package.json +++ b/_extension/package.json @@ -30,6 +30,13 @@ ], "default": "verbose", "description": "Trace TypeScript Go server communication." + }, + "typescript-go.memoryLimit": { + "type": "string", + "default": "", + "description": "Memory limit for the TypeScript Go language server (e.g., '2GiB', '500MiB'). Leave empty for no limit.", + "pattern": "^([0-9]+(\\.[0-9]+)?([KMGT]i)?B)?$", + "patternErrorMessage": "Must be a valid memory size (e.g., '2GiB', '500MiB', '1024B')" } } } diff --git a/_extension/src/extension.ts b/_extension/src/extension.ts index 3f3f46e7ac..6a5b94c1a5 100644 --- a/_extension/src/extension.ts +++ b/_extension/src/extension.ts @@ -39,6 +39,9 @@ export function activate(context: vscode.ExtensionContext) { }, }; + const config = vscode.workspace.getConfiguration("typescript-go"); + const memoryLimit = config.get("memoryLimit"); + const clientOptions: LanguageClientOptions = { documentSelector: [ { scheme: "file", language: "typescript" }, @@ -52,6 +55,7 @@ export function activate(context: vscode.ExtensionContext) { ], outputChannel: output, traceOutputChannel: traceOutput, + ...(memoryLimit ? { initializationOptions: { memoryLimit: memoryLimit } } : {}), diagnosticPullOptions: { onChange: true, onSave: true, diff --git a/internal/lsp/server.go b/internal/lsp/server.go index d3200178d5..e320776103 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -5,6 +5,8 @@ import ( "errors" "fmt" "io" + "runtime/debug" + "strconv" "strings" "time" @@ -211,6 +213,21 @@ func (s *Server) handleMessage(req *lsproto.RequestMessage) error { func (s *Server) handleInitialize(req *lsproto.RequestMessage) error { s.initializeParams = req.Params.(*lsproto.InitializeParams) + + // Handle memory limit from initialization options + if s.initializeParams.InitializationOptions != nil { + if opts, ok := (*s.initializeParams.InitializationOptions).(map[string]any); ok { + if memLimitStr, ok := opts["memoryLimit"].(string); ok && memLimitStr != "" { + if limit, err := parseMemoryLimit(memLimitStr); err != nil { + s.Log("Failed to parse memory limit:", err) + } else { + debug.SetMemoryLimit(limit) + s.Log(fmt.Sprintf("Set memory limit to %s (%d bytes)", memLimitStr, limit)) + } + } + } + } + return s.sendResult(req.ID, &lsproto.InitializeResult{ ServerInfo: &lsproto.ServerInfo{ Name: "typescript-go", @@ -397,3 +414,36 @@ func codeFence(lang string, code string) string { } return result.String() } + +// parseMemoryLimit parses a memory limit string (e.g., "2GiB", "500MiB") into bytes. +// Supports: B, KiB, MiB, GiB, TiB (IEC binary prefixes). +func parseMemoryLimit(s string) (int64, error) { + if s == "" { + return -1, fmt.Errorf("empty memory limit") + } + + multipliers := map[string]int64{ + "B": 1, + "KiB": 1024, + "MiB": 1024 * 1024, + "GiB": 1024 * 1024 * 1024, + "TiB": 1024 * 1024 * 1024 * 1024, + } + + // Try each suffix + for suffix, multiplier := range multipliers { + if strings.HasSuffix(s, suffix) { + numStr := strings.TrimSuffix(s, suffix) + num, err := strconv.ParseFloat(numStr, 64) + if err != nil { + return -1, fmt.Errorf("invalid number in memory limit %q: %w", s, err) + } + if num <= 0 { + return -1, fmt.Errorf("memory limit must be positive: %q", s) + } + return int64(num * float64(multiplier)), nil + } + } + + return -1, fmt.Errorf("invalid memory limit format %q (expected suffix: B, KiB, MiB, GiB, or TiB)", s) +}