From 73ca774af242dd0ea563043d5c2f86e37d980eb9 Mon Sep 17 00:00:00 2001 From: Johnny Cheng Date: Tue, 26 Aug 2025 15:32:52 +1000 Subject: [PATCH 1/6] implement custom config --- _extension/package.json | 5 ++++ _extension/src/client.ts | 9 ++++++- _extension/src/extension.ts | 5 ++-- internal/lsp/server.go | 25 +++++++++++++------ internal/project/configfileregistrybuilder.go | 13 ++++++++++ internal/project/session.go | 1 + 6 files changed, 48 insertions(+), 10 deletions(-) diff --git a/_extension/package.json b/_extension/package.json index 8aca1f4c3d..be929cd77d 100644 --- a/_extension/package.json +++ b/_extension/package.json @@ -31,6 +31,11 @@ { "title": "TypeScript Native Preview", "properties": { + "typescript.native-preview.customConfigFileName": { + "type": "string", + "description": "Custom config file name to use, before defaulting to tsconfig.json/jsconfig.json.", + "tags": ["experimental"] + }, "typescript.native-preview.trace.server": { "type": "string", "enum": [ diff --git a/_extension/src/client.ts b/_extension/src/client.ts index 46fd90ebb0..6be812c2db 100644 --- a/_extension/src/client.ts +++ b/_extension/src/client.ts @@ -94,6 +94,7 @@ export class Client { const config = vscode.workspace.getConfiguration("typescript.native-preview"); const pprofDir = config.get("pprofDir"); const pprofArgs = pprofDir ? ["--pprofDir", pprofDir] : []; + const customConfigFileName = config.get("customConfigFileName") ?? ""; const serverOptions: ServerOptions = { run: { @@ -112,7 +113,13 @@ export class Client { "typescript.native-preview", "typescript.native-preview-lsp", serverOptions, - this.clientOptions, + // could probs move this to constructor. + { + ...this.clientOptions, + initializationOptions: { + customConfigFileName, + }, + }, ); this.outputChannel.appendLine(`Starting language server...`); diff --git a/_extension/src/extension.ts b/_extension/src/extension.ts index 784ebee7fc..0c753faa0c 100644 --- a/_extension/src/extension.ts +++ b/_extension/src/extension.ts @@ -14,11 +14,12 @@ export async function activate(context: vscode.ExtensionContext) { registerCommands(context, client, output, traceOutput); context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(event => { - if (event.affectsConfiguration("typescript.experimental.useTsgo")) { + if (event.affectsConfiguration("typescript.experimental.useTsgo") || + event.affectsConfiguration("typescript.native-preview.configNames")) { // Delay because the command to change the config setting will restart // the extension host, so no need to show a message setTimeout(async () => { - const selected = await vscode.window.showInformationMessage("TypeScript Native Preview setting has changed. Restart extensions to apply changes.", "Restart Extensions"); + const selected = await vscode.window.showInformationMessage("TypeScript Native Preview setting's have changed. Restart extensions to apply changes.", "Restart Extensions"); if (selected) { vscode.commands.executeCommand("workbench.action.restartExtensionHost"); } diff --git a/internal/lsp/server.go b/internal/lsp/server.go index ed1a15ddb6..1dc60391fb 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -628,15 +628,26 @@ func (s *Server) handleInitialized(ctx context.Context, params *lsproto.Initiali s.watchEnabled = true } + // Read customConfigFileName from initializationOptions if provided + var customConfigFileName string + if init := s.initializeParams; init != nil && init.InitializationOptions != nil { + if m, ok := (*init.InitializationOptions).(map[string]any); ok { + if v, ok := m["customConfigFileName"].(string); ok { + customConfigFileName = v + } + } + } + s.session = project.NewSession(&project.SessionInit{ Options: &project.SessionOptions{ - CurrentDirectory: s.cwd, - DefaultLibraryPath: s.defaultLibraryPath, - TypingsLocation: s.typingsLocation, - PositionEncoding: s.positionEncoding, - WatchEnabled: s.watchEnabled, - LoggingEnabled: true, - DebounceDelay: 500 * time.Millisecond, + CurrentDirectory: s.cwd, + DefaultLibraryPath: s.defaultLibraryPath, + TypingsLocation: s.typingsLocation, + PositionEncoding: s.positionEncoding, + WatchEnabled: s.watchEnabled, + LoggingEnabled: true, + DebounceDelay: 500 * time.Millisecond, + CustomConfigFileName: customConfigFileName, }, FS: s.fs, Logger: s.logger, diff --git a/internal/project/configfileregistrybuilder.go b/internal/project/configfileregistrybuilder.go index 725bc343a8..53260d2f1a 100644 --- a/internal/project/configfileregistrybuilder.go +++ b/internal/project/configfileregistrybuilder.go @@ -409,6 +409,19 @@ func (c *configFileRegistryBuilder) handleConfigChange(entry *dirty.SyncMapEntry func (c *configFileRegistryBuilder) computeConfigFileName(fileName string, skipSearchInDirectoryOfFile bool, logger *logging.LogTree) string { searchPath := tspath.GetDirectoryPath(fileName) + // If custom config file is provided, search for it in directory of file and its ancestors first. + if c.sessionOptions.CustomConfigFileName != "" { + result, _ := tspath.ForEachAncestorDirectory(searchPath, func(directory string) (result string, stop bool) { + customConfigFilePath := tspath.CombinePaths(directory, c.sessionOptions.CustomConfigFileName) + if !skipSearchInDirectoryOfFile && c.FS().FileExists(customConfigFilePath) { + return customConfigFilePath, true + } + return "", false + }) + logger.Logf("computeConfigFileName:: File: %s:: Result: %s", fileName, result) + return result + } + result, _ := tspath.ForEachAncestorDirectory(searchPath, func(directory string) (result string, stop bool) { tsconfigPath := tspath.CombinePaths(directory, "tsconfig.json") if !skipSearchInDirectoryOfFile && c.FS().FileExists(tsconfigPath) { diff --git a/internal/project/session.go b/internal/project/session.go index fabaf9a3c0..c0cda57fec 100644 --- a/internal/project/session.go +++ b/internal/project/session.go @@ -31,6 +31,7 @@ type SessionOptions struct { WatchEnabled bool LoggingEnabled bool DebounceDelay time.Duration + CustomConfigFileName string } type SessionInit struct { From 4a69d4c49820ce0a2326a8e85b9a61495d654d17 Mon Sep 17 00:00:00 2001 From: Johnny Cheng Date: Tue, 26 Aug 2025 16:07:39 +1000 Subject: [PATCH 2/6] cleanup --- _extension/src/client.ts | 1 - _extension/src/extension.ts | 2 +- internal/lsp/server.go | 14 +++++++------- internal/project/configfileregistrybuilder.go | 1 + 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/_extension/src/client.ts b/_extension/src/client.ts index 6be812c2db..be4ee4571e 100644 --- a/_extension/src/client.ts +++ b/_extension/src/client.ts @@ -113,7 +113,6 @@ export class Client { "typescript.native-preview", "typescript.native-preview-lsp", serverOptions, - // could probs move this to constructor. { ...this.clientOptions, initializationOptions: { diff --git a/_extension/src/extension.ts b/_extension/src/extension.ts index 0c753faa0c..9e664a282c 100644 --- a/_extension/src/extension.ts +++ b/_extension/src/extension.ts @@ -19,7 +19,7 @@ export async function activate(context: vscode.ExtensionContext) { // Delay because the command to change the config setting will restart // the extension host, so no need to show a message setTimeout(async () => { - const selected = await vscode.window.showInformationMessage("TypeScript Native Preview setting's have changed. Restart extensions to apply changes.", "Restart Extensions"); + const selected = await vscode.window.showInformationMessage("TypeScript Native Preview settings have changed. Restart extensions to apply changes.", "Restart Extensions"); if (selected) { vscode.commands.executeCommand("workbench.action.restartExtensionHost"); } diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 1dc60391fb..3b353241b0 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -640,13 +640,13 @@ func (s *Server) handleInitialized(ctx context.Context, params *lsproto.Initiali s.session = project.NewSession(&project.SessionInit{ Options: &project.SessionOptions{ - CurrentDirectory: s.cwd, - DefaultLibraryPath: s.defaultLibraryPath, - TypingsLocation: s.typingsLocation, - PositionEncoding: s.positionEncoding, - WatchEnabled: s.watchEnabled, - LoggingEnabled: true, - DebounceDelay: 500 * time.Millisecond, + CurrentDirectory: s.cwd, + DefaultLibraryPath: s.defaultLibraryPath, + TypingsLocation: s.typingsLocation, + PositionEncoding: s.positionEncoding, + WatchEnabled: s.watchEnabled, + LoggingEnabled: true, + DebounceDelay: 500 * time.Millisecond, CustomConfigFileName: customConfigFileName, }, FS: s.fs, diff --git a/internal/project/configfileregistrybuilder.go b/internal/project/configfileregistrybuilder.go index 53260d2f1a..bb5d7bf758 100644 --- a/internal/project/configfileregistrybuilder.go +++ b/internal/project/configfileregistrybuilder.go @@ -416,6 +416,7 @@ func (c *configFileRegistryBuilder) computeConfigFileName(fileName string, skipS if !skipSearchInDirectoryOfFile && c.FS().FileExists(customConfigFilePath) { return customConfigFilePath, true } + skipSearchInDirectoryOfFile = false return "", false }) logger.Logf("computeConfigFileName:: File: %s:: Result: %s", fileName, result) From a8b1e7ec67eeac12b238c8aad26ca5a68821adab Mon Sep 17 00:00:00 2001 From: Johnny Cheng Date: Tue, 26 Aug 2025 16:09:18 +1000 Subject: [PATCH 3/6] cleanup --- _extension/src/extension.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_extension/src/extension.ts b/_extension/src/extension.ts index 9e664a282c..cdd54916be 100644 --- a/_extension/src/extension.ts +++ b/_extension/src/extension.ts @@ -15,7 +15,7 @@ export async function activate(context: vscode.ExtensionContext) { context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(event => { if (event.affectsConfiguration("typescript.experimental.useTsgo") || - event.affectsConfiguration("typescript.native-preview.configNames")) { + event.affectsConfiguration("typescript.native-preview.customConfigFileName")) { // Delay because the command to change the config setting will restart // the extension host, so no need to show a message setTimeout(async () => { From b0447fdeae73ef90a78987c607ba6160c0b11518 Mon Sep 17 00:00:00 2001 From: Johnny Cheng Date: Tue, 26 Aug 2025 16:12:17 +1000 Subject: [PATCH 4/6] better logic --- internal/project/configfileregistrybuilder.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/internal/project/configfileregistrybuilder.go b/internal/project/configfileregistrybuilder.go index bb5d7bf758..bcaf961864 100644 --- a/internal/project/configfileregistrybuilder.go +++ b/internal/project/configfileregistrybuilder.go @@ -410,8 +410,10 @@ func (c *configFileRegistryBuilder) handleConfigChange(entry *dirty.SyncMapEntry func (c *configFileRegistryBuilder) computeConfigFileName(fileName string, skipSearchInDirectoryOfFile bool, logger *logging.LogTree) string { searchPath := tspath.GetDirectoryPath(fileName) // If custom config file is provided, search for it in directory of file and its ancestors first. + // If a custom config file is provided and not found, default to tsconfig.json/jsconfig.json. + var result string if c.sessionOptions.CustomConfigFileName != "" { - result, _ := tspath.ForEachAncestorDirectory(searchPath, func(directory string) (result string, stop bool) { + result, _ = tspath.ForEachAncestorDirectory(searchPath, func(directory string) (result string, stop bool) { customConfigFilePath := tspath.CombinePaths(directory, c.sessionOptions.CustomConfigFileName) if !skipSearchInDirectoryOfFile && c.FS().FileExists(customConfigFilePath) { return customConfigFilePath, true @@ -419,11 +421,14 @@ func (c *configFileRegistryBuilder) computeConfigFileName(fileName string, skipS skipSearchInDirectoryOfFile = false return "", false }) + } + + if result != "" { logger.Logf("computeConfigFileName:: File: %s:: Result: %s", fileName, result) return result } - result, _ := tspath.ForEachAncestorDirectory(searchPath, func(directory string) (result string, stop bool) { + result, _ = tspath.ForEachAncestorDirectory(searchPath, func(directory string) (result string, stop bool) { tsconfigPath := tspath.CombinePaths(directory, "tsconfig.json") if !skipSearchInDirectoryOfFile && c.FS().FileExists(tsconfigPath) { return tsconfigPath, true From d958cdee3a40b0fea8a31859ef66e4608d3d95e5 Mon Sep 17 00:00:00 2001 From: Johnny Cheng Date: Wed, 27 Aug 2025 15:40:29 +1000 Subject: [PATCH 5/6] fix panic --- internal/lsp/server.go | 14 +++++++------- internal/project/configfileregistrybuilder.go | 16 ++++++++++------ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 3b353241b0..1dc60391fb 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -640,13 +640,13 @@ func (s *Server) handleInitialized(ctx context.Context, params *lsproto.Initiali s.session = project.NewSession(&project.SessionInit{ Options: &project.SessionOptions{ - CurrentDirectory: s.cwd, - DefaultLibraryPath: s.defaultLibraryPath, - TypingsLocation: s.typingsLocation, - PositionEncoding: s.positionEncoding, - WatchEnabled: s.watchEnabled, - LoggingEnabled: true, - DebounceDelay: 500 * time.Millisecond, + CurrentDirectory: s.cwd, + DefaultLibraryPath: s.defaultLibraryPath, + TypingsLocation: s.typingsLocation, + PositionEncoding: s.positionEncoding, + WatchEnabled: s.watchEnabled, + LoggingEnabled: true, + DebounceDelay: 500 * time.Millisecond, CustomConfigFileName: customConfigFileName, }, FS: s.fs, diff --git a/internal/project/configfileregistrybuilder.go b/internal/project/configfileregistrybuilder.go index bcaf961864..56cecb32f8 100644 --- a/internal/project/configfileregistrybuilder.go +++ b/internal/project/configfileregistrybuilder.go @@ -409,16 +409,17 @@ func (c *configFileRegistryBuilder) handleConfigChange(entry *dirty.SyncMapEntry func (c *configFileRegistryBuilder) computeConfigFileName(fileName string, skipSearchInDirectoryOfFile bool, logger *logging.LogTree) string { searchPath := tspath.GetDirectoryPath(fileName) + skip := skipSearchInDirectoryOfFile + var result string // If custom config file is provided, search for it in directory of file and its ancestors first. // If a custom config file is provided and not found, default to tsconfig.json/jsconfig.json. - var result string if c.sessionOptions.CustomConfigFileName != "" { result, _ = tspath.ForEachAncestorDirectory(searchPath, func(directory string) (result string, stop bool) { customConfigFilePath := tspath.CombinePaths(directory, c.sessionOptions.CustomConfigFileName) - if !skipSearchInDirectoryOfFile && c.FS().FileExists(customConfigFilePath) { + if !skip && c.FS().FileExists(customConfigFilePath) { return customConfigFilePath, true } - skipSearchInDirectoryOfFile = false + skip = false return "", false }) } @@ -428,19 +429,22 @@ func (c *configFileRegistryBuilder) computeConfigFileName(fileName string, skipS return result } + // Reset skip to original value. + skip = skipSearchInDirectoryOfFile + result, _ = tspath.ForEachAncestorDirectory(searchPath, func(directory string) (result string, stop bool) { tsconfigPath := tspath.CombinePaths(directory, "tsconfig.json") - if !skipSearchInDirectoryOfFile && c.FS().FileExists(tsconfigPath) { + if !skip && c.FS().FileExists(tsconfigPath) { return tsconfigPath, true } jsconfigPath := tspath.CombinePaths(directory, "jsconfig.json") - if !skipSearchInDirectoryOfFile && c.FS().FileExists(jsconfigPath) { + if !skip && c.FS().FileExists(jsconfigPath) { return jsconfigPath, true } if strings.HasSuffix(directory, "/node_modules") { return "", true } - skipSearchInDirectoryOfFile = false + skip = false return "", false }) logger.Logf("computeConfigFileName:: File: %s:: Result: %s", fileName, result) From 34361365313d2f73f8ca4ff4f88c372db490ddd4 Mon Sep 17 00:00:00 2001 From: Johnny Cheng Date: Wed, 27 Aug 2025 15:49:37 +1000 Subject: [PATCH 6/6] copilot --- internal/project/configfileregistrybuilder.go | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/internal/project/configfileregistrybuilder.go b/internal/project/configfileregistrybuilder.go index 56cecb32f8..bd3dc8f83c 100644 --- a/internal/project/configfileregistrybuilder.go +++ b/internal/project/configfileregistrybuilder.go @@ -409,17 +409,17 @@ func (c *configFileRegistryBuilder) handleConfigChange(entry *dirty.SyncMapEntry func (c *configFileRegistryBuilder) computeConfigFileName(fileName string, skipSearchInDirectoryOfFile bool, logger *logging.LogTree) string { searchPath := tspath.GetDirectoryPath(fileName) - skip := skipSearchInDirectoryOfFile + skipSearchInDirectoryOfFileForCustomConfig := skipSearchInDirectoryOfFile var result string // If custom config file is provided, search for it in directory of file and its ancestors first. // If a custom config file is provided and not found, default to tsconfig.json/jsconfig.json. if c.sessionOptions.CustomConfigFileName != "" { result, _ = tspath.ForEachAncestorDirectory(searchPath, func(directory string) (result string, stop bool) { customConfigFilePath := tspath.CombinePaths(directory, c.sessionOptions.CustomConfigFileName) - if !skip && c.FS().FileExists(customConfigFilePath) { + if !skipSearchInDirectoryOfFileForCustomConfig && c.FS().FileExists(customConfigFilePath) { return customConfigFilePath, true } - skip = false + skipSearchInDirectoryOfFileForCustomConfig = false return "", false }) } @@ -429,22 +429,19 @@ func (c *configFileRegistryBuilder) computeConfigFileName(fileName string, skipS return result } - // Reset skip to original value. - skip = skipSearchInDirectoryOfFile - result, _ = tspath.ForEachAncestorDirectory(searchPath, func(directory string) (result string, stop bool) { tsconfigPath := tspath.CombinePaths(directory, "tsconfig.json") - if !skip && c.FS().FileExists(tsconfigPath) { + if !skipSearchInDirectoryOfFile && c.FS().FileExists(tsconfigPath) { return tsconfigPath, true } jsconfigPath := tspath.CombinePaths(directory, "jsconfig.json") - if !skip && c.FS().FileExists(jsconfigPath) { + if !skipSearchInDirectoryOfFile && c.FS().FileExists(jsconfigPath) { return jsconfigPath, true } if strings.HasSuffix(directory, "/node_modules") { return "", true } - skip = false + skipSearchInDirectoryOfFile = false return "", false }) logger.Logf("computeConfigFileName:: File: %s:: Result: %s", fileName, result)