package project import ( "maps" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/core" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/lsp/lsproto" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/tsoptions" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath" ) type ConfigFileRegistry struct { // configs is a map of config file paths to their entries. configs map[tspath.Path]*configFileEntry // configFileNames is a map of open file paths to information // about their ancestor config file names. It is only used as // a cache during configFileNames map[tspath.Path]*configFileNames } type configFileEntry struct { pendingReload PendingReload commandLine *tsoptions.ParsedCommandLine // retainingProjects is the set of projects that have called acquireConfig // without releasing it. A config file entry may be acquired by a project // either because it is the config for that project or because it is the // config for a referenced project. retainingProjects map[tspath.Path]struct{} // retainingOpenFiles is the set of open files that caused this config to // load during project collection building. This config file may or may not // end up being the config for the default project for these files, but // determining the default project loaded this config as a candidate, so // subsequent calls to `projectCollectionBuilder.findDefaultConfiguredProject` // will use this config as part of the search, so it must be retained. retainingOpenFiles map[tspath.Path]struct{} // retainingConfigs is the set of config files that extend this one. This // provides a cheap reverse mapping for a project config's // `commandLine.ExtendedSourceFiles()` that can be used to notify the // extending projects when this config changes. An extended config file may // or may not also be used directly by a project, so it's possible that // when this is set, no other fields will be used. retainingConfigs map[tspath.Path]struct{} // rootFilesWatch is a watch for the root files of this config file. rootFilesWatch *WatchedFiles[patternsAndIgnored] } func newConfigFileEntry(fileName string) *configFileEntry { return &configFileEntry{ pendingReload: PendingReloadFull, rootFilesWatch: NewWatchedFiles( "root files for "+fileName, lsproto.WatchKindCreate|lsproto.WatchKindChange|lsproto.WatchKindDelete, core.Identity, ), } } func newExtendedConfigFileEntry(extendingConfigPath tspath.Path) *configFileEntry { return &configFileEntry{ pendingReload: PendingReloadFull, retainingConfigs: map[tspath.Path]struct{}{extendingConfigPath: {}}, } } func (e *configFileEntry) Clone() *configFileEntry { return &configFileEntry{ pendingReload: e.pendingReload, commandLine: e.commandLine, // !!! eagerly cloning these maps makes everything more convenient, // but it could be avoided if needed. retainingProjects: maps.Clone(e.retainingProjects), retainingOpenFiles: maps.Clone(e.retainingOpenFiles), retainingConfigs: maps.Clone(e.retainingConfigs), rootFilesWatch: e.rootFilesWatch, } } func (c *ConfigFileRegistry) GetConfig(path tspath.Path) *tsoptions.ParsedCommandLine { if entry, ok := c.configs[path]; ok { return entry.commandLine } return nil } func (c *ConfigFileRegistry) GetConfigFileName(path tspath.Path) string { if entry, ok := c.configFileNames[path]; ok { return entry.nearestConfigFileName } return "" } func (c *ConfigFileRegistry) GetAncestorConfigFileName(path tspath.Path, higherThanConfig string) string { if entry, ok := c.configFileNames[path]; ok { return entry.ancestors[higherThanConfig] } return "" } // clone creates a shallow copy of the configFileRegistry. func (c *ConfigFileRegistry) clone() *ConfigFileRegistry { return &ConfigFileRegistry{ configs: maps.Clone(c.configs), configFileNames: maps.Clone(c.configFileNames), } } type configFileNames struct { // nearestConfigFileName is the file name of the nearest ancestor config file. nearestConfigFileName string // ancestors is a map from one ancestor config file path to the next. // For example, if `/a`, `/a/b`, and `/a/b/c` all contain config files, // the fully loaded map will look like: // { // "/a/b/c/tsconfig.json": "/a/b/tsconfig.json", // "/a/b/tsconfig.json": "/a/tsconfig.json" // } ancestors map[string]string } func (c *configFileNames) Clone() *configFileNames { return &configFileNames{ nearestConfigFileName: c.nearestConfigFileName, ancestors: maps.Clone(c.ancestors), } }