diff --git a/internal/charts/chart_store.go b/internal/charts/chart_store.go index 174d6d3f..625bcf1e 100644 --- a/internal/charts/chart_store.go +++ b/internal/charts/chart_store.go @@ -1,6 +1,8 @@ package charts import ( + "path/filepath" + "github.com/mrjosh/helm-ls/internal/util" "go.lsp.dev/uri" ) @@ -29,8 +31,8 @@ func (s *ChartStore) SetValuesFilesConfig(valuesFilesConfig util.ValuesFilesConf return } s.valuesFilesConfig = valuesFilesConfig - for _, chart := range s.Charts { - chart.ValuesFiles = NewValuesFiles(chart.RootURI, valuesFilesConfig.MainValuesFileName, valuesFilesConfig.LintOverlayValuesFileName, valuesFilesConfig.AdditionalValuesFilesGlobPattern) + for uri := range s.Charts { + s.Charts[uri] = s.newChart(uri, valuesFilesConfig) } } @@ -54,3 +56,17 @@ func (s *ChartStore) GetChartForURI(fileURI uri.URI) (*Chart, error) { URI: fileURI, } } + +func (s *ChartStore) ReloadValuesFile(file uri.URI) { + logger.Println("Reloading values file", file) + chart, err := s.GetChartForURI(uri.URI(util.FileURIScheme + filepath.Dir(file.Filename()))) + if err != nil { + logger.Error("Error reloading values file", file, err) + return + } + for _, valuesFile := range chart.ValuesFiles.AllValuesFiles() { + if valuesFile.URI == file { + valuesFile.Reload() + } + } +} diff --git a/internal/charts/chart_store_test.go b/internal/charts/chart_store_test.go index 740b72ff..75bea663 100644 --- a/internal/charts/chart_store_test.go +++ b/internal/charts/chart_store_test.go @@ -9,6 +9,7 @@ import ( "github.com/mrjosh/helm-ls/pkg/chartutil" "github.com/stretchr/testify/assert" "go.lsp.dev/uri" + "gopkg.in/yaml.v3" ) func TestSetValuesFilesConfigOverwrites(t *testing.T) { @@ -26,10 +27,13 @@ func TestSetValuesFilesConfigOverwrites(t *testing.T) { _ = os.WriteFile(filepath.Join(tempDir, "values.other.yaml"), []byte(valuesContent), 0o644) s := NewChartStore(uri.New(util.FileURIScheme+tempDir), NewChart) - chart, err := s.GetChartForURI(uri.New(util.FileURIScheme + tempDir)) - assert.Equal(t, chartutil.Values{}, chart.ValuesFiles.MainValuesFile.Values) + chartOld, err := s.GetChartForURI(uri.New(util.FileURIScheme + tempDir)) + assert.Equal(t, chartutil.Values{}, chartOld.ValuesFiles.MainValuesFile.Values) s.SetValuesFilesConfig(valuesFilesConfig) + chart, err := s.GetChartForURI(uri.New(util.FileURIScheme + tempDir)) + assert.NoError(t, err) + assert.NotSame(t, chartOld, chart) assert.NoError(t, err) assert.NotEqual(t, chartutil.Values{}, chart.ValuesFiles.MainValuesFile.Values) assert.Equal(t, valuesFilesConfig.MainValuesFileName, filepath.Base(chart.ValuesFiles.MainValuesFile.URI.Filename())) @@ -73,3 +77,28 @@ func TestGetChartForURIWhenChartYamlDoesNotExist(t *testing.T) { assert.Error(t, err) assert.Nil(t, chart) } + +func TestReloadValuesFiles(t *testing.T) { + tempDir := t.TempDir() + chart := &Chart{ + ValuesFiles: &ValuesFiles{MainValuesFile: &ValuesFile{ + Values: map[string]interface{}{"foo": "bar"}, + ValueNode: yaml.Node{}, + URI: uri.New(util.FileURIScheme + filepath.Join(tempDir, "values.yaml")), + }, OverlayValuesFile: &ValuesFile{}, AdditionalValuesFiles: []*ValuesFile{}}, + ChartMetadata: &ChartMetadata{}, + RootURI: uri.New("file://" + tempDir), + ParentChart: ParentChart{}, + } + s := NewChartStore(uri.New(util.FileURIScheme+tempDir), NewChart) + s.Charts[chart.RootURI] = chart + + assert.Equal(t, "bar", chart.ValuesFiles.MainValuesFile.Values["foo"]) + os.WriteFile(filepath.Join(tempDir, "values.yaml"), []byte("foo: new"), 0o644) + + s.ReloadValuesFile(uri.New(util.FileURIScheme + filepath.Join(tempDir, "values.yaml"))) + assert.Equal(t, "new", chart.ValuesFiles.MainValuesFile.Values["foo"]) + + s.ReloadValuesFile(uri.New(util.FileURIScheme + filepath.Join(tempDir, "notfound.yaml"))) + s.ReloadValuesFile(uri.New(util.FileURIScheme + "/notFound.yaml")) +} diff --git a/internal/charts/values_file.go b/internal/charts/values_file.go index b092ce88..5e710738 100644 --- a/internal/charts/values_file.go +++ b/internal/charts/values_file.go @@ -15,7 +15,24 @@ type ValuesFile struct { } func NewValuesFile(filePath string) *ValuesFile { + vals, valueNodes := readInValuesFile(filePath) + return &ValuesFile{ + ValueNode: valueNodes, + Values: vals, + URI: uri.New(util.FileURIScheme + filePath), + } +} + +func (v *ValuesFile) Reload() { + vals, valueNodes := readInValuesFile(v.URI.Filename()) + + logger.Debug("Reloading values file", v.URI.Filename(), vals) + v.Values = vals + v.ValueNode = valueNodes +} + +func readInValuesFile(filePath string) (chartutil.Values, yaml.Node) { vals, err := chartutil.ReadValuesFile(filePath) if err != nil { logger.Error("Error loading values file ", filePath, err) @@ -25,10 +42,5 @@ func NewValuesFile(filePath string) *ValuesFile { if err != nil { logger.Error("Error loading values file ", filePath, err) } - - return &ValuesFile{ - ValueNode: valueNodes, - Values: vals, - URI: uri.New(util.FileURIScheme + filePath), - } + return vals, valueNodes } diff --git a/internal/charts/values_file_test.go b/internal/charts/values_file_test.go index 899e6a3a..58efbe56 100644 --- a/internal/charts/values_file_test.go +++ b/internal/charts/values_file_test.go @@ -30,3 +30,18 @@ func TestNewValuesFileFileNotFound(t *testing.T) { assert.Equal(t, chartutil.Values{}, valuesFile.Values) assert.Equal(t, yaml.Node{}, valuesFile.ValueNode) } + +func TestReload(t *testing.T) { + tempDir := t.TempDir() + valuesContent := `foo: bar` + _ = os.WriteFile(filepath.Join(tempDir, "values.yaml"), []byte(valuesContent), 0o644) + valuesFile := charts.NewValuesFile(filepath.Join(tempDir, "values.yaml")) + + assert.Equal(t, "bar", valuesFile.Values["foo"]) + assert.NotEqual(t, yaml.Node{}, valuesFile.ValueNode) + + _ = os.WriteFile(filepath.Join(tempDir, "values.yaml"), []byte("foo: baz"), 0o644) + valuesFile.Reload() + assert.Equal(t, "baz", valuesFile.Values["foo"]) + assert.NotEqual(t, yaml.Node{}, valuesFile.ValueNode) +} diff --git a/internal/charts/values_files.go b/internal/charts/values_files.go index 561513f9..ba9bf9b5 100644 --- a/internal/charts/values_files.go +++ b/internal/charts/values_files.go @@ -17,9 +17,8 @@ type ValuesFiles struct { func NewValuesFiles(rootURI uri.URI, mainValuesFileName string, lintOverlayValuesFile string, additionalValuesFilesGlob string) *ValuesFiles { additionalValuesFiles := getAdditionalValuesFiles(additionalValuesFilesGlob, rootURI, mainValuesFileName) - var overlayValuesFile *ValuesFile - overlayValuesFile = getLintOverlayValuesFile(lintOverlayValuesFile, additionalValuesFiles, overlayValuesFile, rootURI) + overlayValuesFile := getLintOverlayValuesFile(lintOverlayValuesFile, additionalValuesFiles, rootURI) return &ValuesFiles{ MainValuesFile: NewValuesFile(filepath.Join(rootURI.Filename(), mainValuesFileName)), @@ -28,7 +27,7 @@ func NewValuesFiles(rootURI uri.URI, mainValuesFileName string, lintOverlayValue } } -func getLintOverlayValuesFile(lintOverlayValuesFile string, additionalValuesFiles []*ValuesFile, overlayValuesFile *ValuesFile, rootURI uri.URI) *ValuesFile { +func getLintOverlayValuesFile(lintOverlayValuesFile string, additionalValuesFiles []*ValuesFile, rootURI uri.URI) (overlayValuesFile *ValuesFile) { if lintOverlayValuesFile == "" && len(additionalValuesFiles) == 1 { overlayValuesFile = additionalValuesFiles[0] } diff --git a/internal/handler/handler.go b/internal/handler/handler.go index b39aace6..f7bc804a 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -68,6 +68,8 @@ func (h *langHandler) handle(ctx context.Context, reply jsonrpc2.Replier, req js return h.handleHover(ctx, reply, req) case lsp.MethodWorkspaceDidChangeConfiguration: return h.handleWorkspaceDidChangeConfiguration(ctx, reply, req) + case lsp.MethodWorkspaceDidChangeWatchedFiles: + return h.handleDidChangeWatchedFiles(ctx, reply, req) default: logger.Debug("Unsupported method", req.Method()) } @@ -80,7 +82,6 @@ func (h *langHandler) handleShutdown(_ context.Context, _ jsonrpc2.Replier, _ js } func (h *langHandler) handleTextDocumentDidOpen(ctx context.Context, reply jsonrpc2.Replier, req jsonrpc2.Request) (err error) { - var params lsp.DidOpenTextDocumentParams if err := json.Unmarshal(req.Params(), ¶ms); err != nil { return reply(ctx, nil, err) diff --git a/internal/handler/initialization.go b/internal/handler/initialization.go index 771be2ef..5e1bf782 100644 --- a/internal/handler/initialization.go +++ b/internal/handler/initialization.go @@ -32,7 +32,7 @@ func (h *langHandler) handleInitialize(ctx context.Context, reply jsonrpc2.Repli } h.yamllsConnector.CallInitialize(workspaceURI) - h.chartStore = charts.NewChartStore(workspaceURI, charts.NewChart) + h.chartStore = charts.NewChartStore(workspaceURI, h.NewChartWithWatchedFiles) return reply(ctx, lsp.InitializeResult{ Capabilities: lsp.ServerCapabilities{ diff --git a/internal/handler/watched_files.go b/internal/handler/watched_files.go new file mode 100644 index 00000000..906ad594 --- /dev/null +++ b/internal/handler/watched_files.go @@ -0,0 +1,68 @@ +package handler + +import ( + "context" + "encoding/json" + + "github.com/mrjosh/helm-ls/internal/charts" + "github.com/mrjosh/helm-ls/internal/util" + lsp "go.lsp.dev/protocol" + "go.lsp.dev/uri" + + "go.lsp.dev/jsonrpc2" +) + +func (h *langHandler) NewChartWithWatchedFiles(rootURI uri.URI, valuesFilesConfig util.ValuesFilesConfig) *charts.Chart { + logger.Debug("NewChartWithWatchedFiles", rootURI, valuesFilesConfig) + chart := charts.NewChart(rootURI, valuesFilesConfig) + + uris := make([]uri.URI, 0) + for _, valuesFile := range chart.ValuesFiles.AllValuesFiles() { + uris = append(uris, valuesFile.URI) + } + + go h.RegisterWatchedFiles(context.Background(), h.connPool, uris) + return chart +} + +func (h *langHandler) RegisterWatchedFiles(ctx context.Context, conn jsonrpc2.Conn, files []uri.URI) { + watchers := make([]lsp.FileSystemWatcher, 0) + + for _, file := range files { + watchers = append(watchers, lsp.FileSystemWatcher{ + GlobPattern: file.Filename(), + }) + } + + var result any + _, err := conn.Call(ctx, "client/registerCapability", lsp.RegistrationParams{ + Registrations: []lsp.Registration{ + { + Method: "workspace/didChangeWatchedFiles", + RegisterOptions: lsp.DidChangeWatchedFilesRegistrationOptions{ + Watchers: watchers, + }, + }, + }, + }, result) + if err != nil { + logger.Error("Error registering watched files", err) + } +} + +func (h *langHandler) handleDidChangeWatchedFiles(ctx context.Context, reply jsonrpc2.Replier, req jsonrpc2.Request) (err error) { + if req.Params() == nil { + return &jsonrpc2.Error{Code: jsonrpc2.InvalidParams} + } + + var params lsp.DidChangeWatchedFilesParams + if err := json.Unmarshal(req.Params(), ¶ms); err != nil { + return err + } + + for _, change := range params.Changes { + h.chartStore.ReloadValuesFile(change.URI) + } + + return reply(ctx, nil, nil) +}