diff --git a/proto/view/settings.go b/proto/view/settings.go new file mode 100644 index 0000000..45fbe45 --- /dev/null +++ b/proto/view/settings.go @@ -0,0 +1,56 @@ +package view + +import ( + "errors" + "fmt" +) + +const ( + additionalProtoDirsKey = "additional-proto-dirs" +) + +type Settings struct { + AdditionalProtoDirs []string +} + +var ( + ErrRepackingSettings = errors.New("failed repacking settings") +) + +func SettingsFromInterface(in interface{}) (*Settings, error) { + settingsMap, ok := in.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("%w: settings should have a map[string]interface{} type", ErrRepackingSettings) + } + + var settings Settings + + if value, ok := settingsMap[additionalProtoDirsKey]; ok { + protoDirs, err := StringsSliceFromInterface(value) + if err != nil { + return nil, fmt.Errorf("%w: %s: key = %s", ErrRepackingSettings, err.Error(), additionalProtoDirsKey) + } + settings.AdditionalProtoDirs = protoDirs + } + + return &settings, nil +} + +func StringsSliceFromInterface(in interface{}) ([]string, error) { + interfaceSlice, ok := in.([]interface{}) + if !ok { + return nil, errors.New("field should have a []interface{} type") + } + + var result []string + + for i, item := range interfaceSlice { + str, ok := item.(string) + if !ok { + return nil, fmt.Errorf("item [%d] should have a string type", i) + } + result = append(result, str) + } + + return result, nil +} diff --git a/proto/view/view.go b/proto/view/view.go index d472dae..936534e 100644 --- a/proto/view/view.go +++ b/proto/view/view.go @@ -38,6 +38,7 @@ type view struct { pbHeaders map[defines.DocumentUri][]string Server *lsp.Server + settings Settings fs fs.FS } @@ -294,6 +295,12 @@ func (v *view) GetDocumentUriFromImportPath(cwd defines.DocumentUri, import_name if v.fs.FileExists(abs_name) { return defines.DocumentUri(uri.New(path.Clean(abs_name))), nil } + for _, additionalProtoDir := range v.settings.AdditionalProtoDirs { + abs_name := path.Join(pos, additionalProtoDir, import_name) + if v.fs.FileExists(abs_name) { + return defines.DocumentUri(uri.New(path.Clean(abs_name))), nil + } + } pos = path.Join(pos, "..") } return res, fmt.Errorf("%w: import %s", ErrNotFound, import_name) @@ -371,6 +378,14 @@ func onInitialized(ctx context.Context, req *defines.InitializeParams) (err erro } func onDidChangeConfiguration(ctx context.Context, req *defines.DidChangeConfigurationParams) (err error) { + if ViewManager == nil { + return nil + } + settings, err := SettingsFromInterface(req.Settings) + if err != nil { + return err + } + ViewManager.settings = *settings return nil } diff --git a/proto/view/view_test.go b/proto/view/view_test.go index cbca629..c955df1 100644 --- a/proto/view/view_test.go +++ b/proto/view/view_test.go @@ -14,6 +14,7 @@ func Test_view_GetDocumentUriFromImportPath(t *testing.T) { tests := []struct { name string existingFiles []string + settings Settings cwd defines.DocumentUri import_name string want defines.DocumentUri @@ -43,12 +44,27 @@ func Test_view_GetDocumentUriFromImportPath(t *testing.T) { want: defines.DocumentUri(""), wantErr: ErrNotFound, }, + { + name: "all sub-directories set via settings.additional-proto-dirs are searched for proto definitions", + existingFiles: []string{ + "/project-dir/api/my-service.proto", + "/project-dir/protobuf-dependencies/google/protobuf/empty.proto", + }, + settings: Settings{ + AdditionalProtoDirs: []string{"protobuf-dependencies"}, + }, + cwd: defines.DocumentUri("file:///project-dir/api/my-service.proto"), + import_name: "google/protobuf/empty.proto", + + want: defines.DocumentUri("file:///project-dir/protobuf-dependencies/google/protobuf/empty.proto"), + wantErr: nil, + }, } for i, tt := range tests { t.Run(fmt.Sprint(i), func(t *testing.T) { mockFS := &MockFS{ExistingFiles: tt.existingFiles} - v := &view{fs: mockFS} + v := &view{fs: mockFS, settings: tt.settings} got, err := v.GetDocumentUriFromImportPath(tt.cwd, tt.import_name) require.ErrorIs(t, err, tt.wantErr)