Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions internal/fourslash/fourslash.go
Original file line number Diff line number Diff line change
Expand Up @@ -782,14 +782,13 @@ func (f *FourslashTest) GetOptions() *lsutil.UserPreferences {

func (f *FourslashTest) Configure(t *testing.T, config *lsutil.UserPreferences) {
// !!!
// Callers to this function may need to consider
// sending a more specific configuration for 'javascript'
// or 'js/ts' as well. For now, we only send 'typescript',
// and most tests probably just want this.
// We send 'js/ts' by default because that is what we expect the primary config to be in vscode and VS (one
// set of preferences for both languages). This should be fine in fourslash since tests that need
// multiple options usually send reconfiguration commands for each `verify` anyways
f.userPreferences = config
sendNotification(t, f, lsproto.WorkspaceDidChangeConfigurationInfo, &lsproto.DidChangeConfigurationParams{
Settings: map[string]any{
"typescript": config,
"js/ts": config,
},
})
}
Expand Down
58 changes: 35 additions & 23 deletions internal/ls/lsutil/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,31 +78,43 @@ func (c *UserConfig) GetPreferences(activeFile string) *UserPreferences {
return NewDefaultUserPreferences()
}

func ParseNewUserConfig(items []any) *UserConfig {
defaultPref := NewUserConfig(NewDefaultUserPreferences())
func ParseNewUserConfig(items map[string]any) *UserConfig {
defaultPref := NewDefaultUserPreferences()
if editorItem, ok := items["editor"]; ok && editorItem != nil {
if editorSettings, ok := editorItem.(map[string]any); ok {
defaultPref.FormatCodeSettings = defaultPref.FormatCodeSettings.ParseEditorSettings(editorSettings)
}
}
if jsTsItem, ok := items["js/ts"]; ok && jsTsItem != nil {
// if "js/ts" is provided, we assume they are already resolved and merged
switch jsTsSettings := jsTsItem.(type) {
case map[string]any:
return NewUserConfig(defaultPref.ParseWorker(jsTsSettings))
case *UserPreferences:
// case for fourslash -- fourslash sends the entire userPreferences over in "js/ts"
return NewUserConfig(jsTsSettings)
}
}

// set typescript and javascript preferences separately
c := &UserConfig{}
for i, item := range items {
if item == nil {
// continue
} else if config, ok := item.(map[string]any); ok {
switch i {
case 0:
// if provided, parse and set "js/ts" as base config
defaultPref = NewUserConfig(defaultPref.ts.ParseWorker(config))
c = defaultPref.Copy()
continue
case 1:
// typescript
c.ts = defaultPref.ts.ParseWorker(config)
case 2:
// javascript
c.js = defaultPref.js.ParseWorker(config)
}
} else if item, ok := item.(*UserPreferences); ok {
// case for fourslash -- fourslash sends the entire userPreferences over
// !!! support format and js/ts distinction?
return NewUserConfig(item)
if tsItem, ok := items["typescript"]; ok && tsItem != nil {
switch tsSettings := tsItem.(type) {
case map[string]any:
c.ts = defaultPref.Copy().ParseWorker(tsSettings)
case *UserPreferences:
c.ts = tsSettings
}
}

if jsItem, ok := items["javascript"]; ok && jsItem != nil {
switch jsSettings := jsItem.(type) {
case map[string]any:
c.js = defaultPref.Copy().ParseWorker(jsSettings)
case *UserPreferences:
c.js = jsSettings
}
}

return c
}
13 changes: 13 additions & 0 deletions internal/ls/lsutil/formatcodeoptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,19 @@ func (settings *FormatCodeSettings) ToLSFormatOptions() *lsproto.FormattingOptio
}
}

func (settings *FormatCodeSettings) ParseEditorSettings(editorSettings map[string]any) *FormatCodeSettings {
if editorSettings == nil {
return settings
}
for name, value := range editorSettings {
switch strings.ToLower(name) {
case "baseindentsize", "indentsize", "tabsize", "newlinecharacter", "converttabstospaces", "indentstyle", "trimtrailingwhitespace":
settings.Set(name, value)
}
}
return settings
}

func (settings *FormatCodeSettings) Parse(prefs any) bool {
formatSettingsMap, ok := prefs.(map[string]any)
formatSettingsParsed := false
Expand Down
5 changes: 4 additions & 1 deletion internal/ls/lsutil/userpreferences.go
Original file line number Diff line number Diff line change
Expand Up @@ -618,8 +618,11 @@ func parseBoolWithDefault(val any, defaultV bool) bool {
}

func parseIntWithDefault(val any, defaultV int) int {
if v, ok := val.(int); ok {
switch v := val.(type) {
case int:
return v
case float64:
return int(v)
}
return defaultV
}
Expand Down
6 changes: 6 additions & 0 deletions internal/lsp/lsproto/_generate/generate.mts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ const customStructures: Structure[] = [
optional: true,
documentation: "The client-side command name that resolved references/implementations `CodeLens` should trigger. Arguments passed will be `(DocumentUri, Position, Location[])`.",
},
{
name: "userPreferences",
type: { kind: "reference", name: "any" },
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel a little nervous about any, but it's probably fine given how we are walking it. In the future I'd like to define a type for this (but that can probably come in my PR if I ever get around to fixing it)

optional: true,
documentation: "userPreferences and/or formatting options if provided at initialization.",
},
],
documentation: "InitializationOptions contains user-provided initialization options.",
},
Expand Down
3 changes: 3 additions & 0 deletions internal/lsp/lsproto/lsp_generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 38 additions & 8 deletions internal/lsp/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,18 @@ func (s *Server) RefreshCodeLens(ctx context.Context) error {
func (s *Server) RequestConfiguration(ctx context.Context) (*lsutil.UserConfig, error) {
caps := lsproto.GetClientCapabilities(ctx)
if !caps.Workspace.Configuration {
// if no configuration request capapbility, return default config
if s.initializeParams != nil && s.initializeParams.InitializationOptions != nil && s.initializeParams.InitializationOptions.UserPreferences != nil {
s.logger.Logf(
"received formatting options from initialization: %T\n%+v",
*s.initializeParams.InitializationOptions.UserPreferences,
*s.initializeParams.InitializationOptions.UserPreferences,
)
// Any options received via initializationOptions will be used for both `js` and `ts` options
if config, ok := (*s.initializeParams.InitializationOptions.UserPreferences).(map[string]any); ok {
return lsutil.NewUserConfig(lsutil.NewDefaultUserPreferences().ParseWorker(config)), nil
}
}
// if no configuration request capability, return default config
return lsutil.NewUserConfig(nil), nil
}
configs, err := sendClientRequest(ctx, s, lsproto.WorkspaceConfigurationInfo, &lsproto.ConfigurationParams{
Expand All @@ -298,12 +309,35 @@ func (s *Server) RequestConfiguration(ctx context.Context) (*lsutil.UserConfig,
{
Section: new("javascript"),
},
{
Section: new("editor"),
},
},
})
if err != nil {
return &lsutil.UserConfig{}, fmt.Errorf("configure request failed: %w", err)
}
return lsutil.ParseNewUserConfig(configs), nil
configMap := map[string]any{}
for i, config := range configs {
switch i {
case 0:
configMap["js/ts"] = config
case 1:
configMap["typescript"] = config
case 2:
configMap["javascript"] = config
case 3:
configMap["editor"] = config
}
}
s.logger.Logf(
"received options from workspace/configuration request:\njs/ts: %+v\n\ntypescript: %+v\n\njavascript: %+v\n\neditor: %+v\n",
configMap["js/ts"],
configMap["typescript"],
configMap["javascript"],
configMap["editor"],
)
return lsutil.ParseNewUserConfig(configMap), nil
}

func (s *Server) Run(ctx context.Context) error {
Expand Down Expand Up @@ -1049,7 +1083,7 @@ func (s *Server) handleInitialized(ctx context.Context, params *lsproto.Initiali
RegisterOptions: &lsproto.RegisterOptions{
DidChangeConfiguration: &lsproto.DidChangeConfigurationRegistrationOptions{
Section: &lsproto.StringOrStrings{
Strings: &[]string{"js/ts", "typescript", "javascript"},
Strings: &[]string{"js/ts", "typescript", "javascript", "editor"},
},
},
},
Expand Down Expand Up @@ -1081,14 +1115,10 @@ func (s *Server) handleExit(ctx context.Context, params any) error {
}

func (s *Server) handleDidChangeWorkspaceConfiguration(ctx context.Context, params *lsproto.DidChangeConfigurationParams) error {
// !!! only implemented because needed for fourslash
if params.Settings == nil {
return nil
} else if settings, ok := params.Settings.([]any); ok {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this actually never send as an array?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assumed vscode would do that because that's what the response was for workspace/configuration, but after testing it in quite a few ways, vscode sends it as an object. VS also sends it as an object, so i saw no reason to keep the unlabeled array format

s.session.Configure(lsutil.ParseNewUserConfig(settings))
} else if settings, ok := params.Settings.(map[string]any); ok {
// fourslash case
s.session.Configure(lsutil.ParseNewUserConfig([]any{settings["js/ts"], settings["typescript"], settings["javascript"]}))
s.session.Configure(lsutil.ParseNewUserConfig(settings))
}
return nil
}
Expand Down
4 changes: 2 additions & 2 deletions internal/project/session_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -922,7 +922,7 @@ func TestSession(t *testing.T) {
"OrganizeImportsIgnoreCase": true,
}
// set "typescript" options only
session.Configure(lsutil.ParseNewUserConfig([]any{nil, configMap1, nil}))
session.Configure(lsutil.ParseNewUserConfig(map[string]any{"typescript": configMap1}))
actualConfig1 := session.Config()
expectedPrefs1 := lsutil.NewDefaultUserPreferences()
expectedPrefs1.UseAliasesForRename = core.TSTrue
Expand All @@ -939,7 +939,7 @@ func TestSession(t *testing.T) {
"OrganizeImportsIgnoreCase": false,
}
// set "javascript" options only
session.Configure(lsutil.ParseNewUserConfig([]any{nil, nil, configMap2}))
session.Configure(lsutil.ParseNewUserConfig(map[string]any{"javascript": configMap2}))
actualConfig2 := session.Config()
expectedPrefs2 := lsutil.NewDefaultUserPreferences()
expectedPrefs2.UseAliasesForRename = core.TSFalse
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ Config File Names::
"method": "workspace/didChangeConfiguration",
"params": {
"settings": {
"typescript": {
"js/ts": {
"FormatCodeSettings": null,
"QuotePreference": "",
"LazyConfiguredProjectsFromExternalProject": false,
Expand Down Expand Up @@ -609,7 +609,7 @@ Config::
"method": "workspace/didChangeConfiguration",
"params": {
"settings": {
"typescript": {
"js/ts": {
"FormatCodeSettings": {
"BaseIndentSize": 0,
"IndentSize": 4,
Expand Down