Skip to content

Commit c54251d

Browse files
committed
internal/golangorgx/gopls: Get cue lsp to initialize with emacs lsp
Emacs does support workspaces, but the initialize handshake can use the RootURI scheme. The upstream Gopls code to handle this is very simple, and so we take the same approach here: converting the RootURI into a workspace, if-and-only-if there are no workspaceFolders provided. We still enforce the requirement that, for now, only one workspace folder is supported. With this change, Emacs finishes the initial handshake with cue lsp, and reports no errors. Additionally (and as a bit of a drive-by), switch a couple more "gopls" strings to "cue lsp" as these show up in some log messages. Signed-off-by: Matthew Sackman <[email protected]> Change-Id: I2971ecb9b124c921b4279c94ef0509d639f1d36c Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1213346 TryBot-Result: CUEcueckoo <[email protected]> Unity-Result: CUE porcuepine <[email protected]> Reviewed-by: Paul Jolly <[email protected]>
1 parent 2824405 commit c54251d

File tree

6 files changed

+138
-40
lines changed

6 files changed

+138
-40
lines changed

Diff for: cmd/cue/cmd/integration/workspace/workspace_folders_test.go

+107-31
Original file line numberDiff line numberDiff line change
@@ -17,39 +17,22 @@ func TestMain(m *testing.M) {
1717
Main(m, hooks.Options)
1818
}
1919

20-
// TestWorkingSimpleModule ensures that we have a successful package load for a
21-
// simple module rooted in the workspace folder with a single CUE file at the
22-
// root.
23-
func TestWorkingSimpleModule(t *testing.T) {
24-
const files = `
20+
// TestWorkspaceFoldersRootURI tests that the server initialization
21+
// works, or fails, as expected, due to various combinations of
22+
// WorkspaceFolders and the RootURI being set or unset.
23+
func TestWorkspaceFoldersRootURI(t *testing.T) {
24+
const filesOneModule = `
2525
-- cue.mod/module.cue --
26-
module: "mod.example"
27-
language: {
28-
version: "v0.11.0"
29-
}
30-
-- a.cue --
26+
module: "mod.example/b"
27+
language: version: "v0.11.0"
28+
29+
-- a/a.cue --
3130
package a
3231
-- b/b.cue --
3332
package c
3433
`
35-
WithOptions().Run(t, files, func(t *testing.T, env *Env) {
36-
// Simulate a change and ensure we get diagnostics back
37-
env.OpenFile("a.cue")
38-
env.EditBuffer("a.cue", fake.NewEdit(1, 0, 1, 0, "\nx: 5\n"))
39-
got := env.BufferText("a.cue")
40-
want := "package a\n\nx: 5\n"
41-
qt.Assert(t, qt.Equals(got, want))
42-
env.Await(env.DoneWithChange())
43-
})
44-
}
45-
46-
// TestMultipleWorkspaceFolders verifies the behaviour of starting 'cue lsp'
47-
// with multiple WorkspaceFolders. This is currently not supported, and hence
48-
// the test is a negative test that asserts 'cue lsp' will fail (during the
49-
// Initialize phase).
50-
func TestMultipleWorkspaceFolders(t *testing.T) {
51-
const files = `
5234

35+
const filesTwoModules = `
5336
-- a/cue.mod/module.cue --
5437
module: "mod.example/b"
5538
language: version: "v0.11.0"
@@ -65,10 +48,103 @@ language: version: "v0.11.0"
6548
package a
6649
6750
`
68-
WithOptions(
69-
WorkspaceFolders("a", "b"),
70-
InitializeError("initialize: got 2 WorkspaceFolders; expected 1"),
71-
).Run(t, files, nil)
51+
52+
type tc struct {
53+
name string
54+
opts []RunOption
55+
files string
56+
expectSuccess bool
57+
}
58+
tests := []tc{
59+
{
60+
// With no workspace folders and no rooturi, the server will
61+
// return an error during initialization.
62+
name: "no workspace folders, no rooturi",
63+
opts: []RunOption{
64+
WorkspaceFolders(),
65+
InitializeError("initialize: got 0 WorkspaceFolders; expected 1"),
66+
},
67+
files: filesOneModule,
68+
expectSuccess: false,
69+
},
70+
{
71+
// If no workspace folders are set, but a rooturi is set, the
72+
// server will treat the rooturi as if it is a workspace
73+
// folder.
74+
name: "no workspace folders, rooturi set",
75+
opts: []RunOption{
76+
WorkspaceFolders(),
77+
RootURIAsDefaultFolder(),
78+
},
79+
files: filesOneModule,
80+
expectSuccess: true,
81+
},
82+
{
83+
// If both workspace folders and rooturi are provided, the
84+
// rooturi is ignored, and only workspace folders are used.
85+
name: "workspace folders, rooturi set",
86+
opts: []RunOption{
87+
WorkspaceFolders("a"),
88+
RootURIAsDefaultFolder(),
89+
},
90+
files: filesOneModule,
91+
expectSuccess: true,
92+
},
93+
{
94+
// By default, the test framework will set one workspace
95+
// folder, and will not set the rooturi.
96+
name: "default workspace folders, no rooturi",
97+
files: filesOneModule,
98+
expectSuccess: true,
99+
},
100+
{
101+
// cue lsp does not currently support multiple workspace folders.
102+
name: "multiple folders, one module",
103+
opts: []RunOption{
104+
WorkspaceFolders("a", "b"),
105+
InitializeError("initialize: got 2 WorkspaceFolders; expected 1"),
106+
},
107+
files: filesOneModule,
108+
expectSuccess: false,
109+
},
110+
{
111+
// cue lsp does not currently support multiple workspace
112+
// folders, even if they correctly refer to different
113+
// modules.
114+
name: "multiple folders, two modules",
115+
opts: []RunOption{
116+
WorkspaceFolders("a", "b"),
117+
InitializeError("initialize: got 2 WorkspaceFolders; expected 1"),
118+
},
119+
files: filesTwoModules,
120+
expectSuccess: false,
121+
},
122+
}
123+
124+
for _, tc := range tests {
125+
t.Run(tc.name, func(t *testing.T) {
126+
hadSuccess := false
127+
WithOptions(tc.opts...).Run(t, tc.files, func(t *testing.T, env *Env) {
128+
hadSuccess = true
129+
if tc.expectSuccess {
130+
// We do a trivial edit here, which must succeed, as a
131+
// means of verifying basic plumbing is working
132+
// correctly.
133+
env.OpenFile("a/a.cue")
134+
env.EditBuffer("a/a.cue", fake.NewEdit(1, 0, 1, 0, "\nx: 5\n"))
135+
got := env.BufferText("a/a.cue")
136+
want := "package a\n\nx: 5\n"
137+
qt.Assert(t, qt.Equals(got, want))
138+
env.Await(env.DoneWithChange())
139+
}
140+
})
141+
if tc.expectSuccess && !hadSuccess {
142+
t.Fatal("Initialisation should have succeeded, but it failed")
143+
} else if !tc.expectSuccess && hadSuccess {
144+
t.Fatal("Initialisation should have failed, but it succeeded")
145+
}
146+
})
147+
}
72148
}
73149

74150
// TODO(myitcv): add a test that verifies we get an error in the case that a

Diff for: internal/golangorgx/gopls/cmd/serve.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,8 @@ func (s *Serve) Run(ctx context.Context, args ...string) error {
132132
addr = fmt.Sprintf(":%v", s.Port)
133133
}
134134
if addr != "" {
135-
log.Printf("Gopls daemon: listening on %s network, address %s...", network, addr)
136-
defer log.Printf("Gopls daemon: exiting")
135+
log.Printf("cue lsp daemon: listening on %s network, address %s...", network, addr)
136+
defer log.Printf("cue lsp daemon: exiting")
137137
return jsonrpc2.ListenAndServe(ctx, network, addr, ss, s.IdleTimeout)
138138
}
139139
stream := jsonrpc2.NewHeaderStream(fakenet.NewConn("stdio", os.Stdin, os.Stdout))

Diff for: internal/golangorgx/gopls/debug/info.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ func VersionInfo() *ServerVersion {
4747
return &ServerVersion{
4848
Version: version.Version(),
4949
BuildInfo: &debug.BuildInfo{
50-
Path: "gopls, built in GOPATH mode",
50+
Path: "cue lsp, built in GOPATH mode",
5151
GoVersion: runtime.Version(),
5252
},
5353
}

Diff for: internal/golangorgx/gopls/server/general.go

+11-5
Original file line numberDiff line numberDiff line change
@@ -79,14 +79,20 @@ func (s *server) Initialize(ctx context.Context, params *protocol.ParamInitializ
7979
// prioritise work to support different clients etc based on bug reports.
8080
//
8181
// Ensure this logic is consistent with [server.DidChangeWorkspaceFolders].
82-
//
83-
// Note that (for now) we do not support a fallback to params.RootURI. We
84-
// might do this in the future.
85-
if l := len(params.WorkspaceFolders); l != 1 {
82+
folders := params.WorkspaceFolders
83+
if len(folders) == 0 {
84+
if params.RootURI != "" {
85+
folders = []protocol.WorkspaceFolder{{
86+
URI: string(params.RootURI),
87+
Name: path.Base(params.RootURI.Path()),
88+
}}
89+
}
90+
}
91+
92+
if l := len(folders); l != 1 {
8693
return nil, fmt.Errorf("got %d WorkspaceFolders; expected 1", l)
8794
}
8895

89-
folders := params.WorkspaceFolders
9096
for _, folder := range folders {
9197
if folder.URI == "" {
9298
return nil, fmt.Errorf("empty WorkspaceFolder.URI")

Diff for: internal/golangorgx/gopls/test/integration/fake/editor.go

+8-1
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ type EditorConfig struct {
9898
// To explicitly send no workspace folders, use an empty (non-nil) slice.
9999
WorkspaceFolders []string
100100

101+
// RootURIAsDefaultFolder configures the RootURI initialization
102+
// parameter to be set to the default workdir root.
103+
RootURIAsDefaultFolder bool
104+
101105
// Whether to edit files with windows line endings.
102106
WindowsLineEndings bool
103107

@@ -291,6 +295,9 @@ func (e *Editor) initialize(ctx context.Context) error {
291295
}
292296
params.InitializationOptions = makeSettings(e.sandbox, config, nil)
293297
params.WorkspaceFolders = makeWorkspaceFolders(e.sandbox, config.WorkspaceFolders)
298+
if config.RootURIAsDefaultFolder {
299+
params.RootURI = e.sandbox.Workdir.URI(string(e.sandbox.Workdir.RelativeTo))
300+
}
294301

295302
capabilities, err := clientCapabilities(config)
296303
if err != nil {
@@ -391,7 +398,7 @@ func (e *Editor) HasCommand(id string) bool {
391398
// makeWorkspaceFolders creates a slice of workspace folders to use for
392399
// this editing session, based on the editor configuration.
393400
func makeWorkspaceFolders(sandbox *Sandbox, paths []string) (folders []protocol.WorkspaceFolder) {
394-
if len(paths) == 0 {
401+
if paths == nil {
395402
paths = []string{string(sandbox.Workdir.RelativeTo)}
396403
}
397404

Diff for: internal/golangorgx/gopls/test/integration/options.go

+9
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,15 @@ func WorkspaceFolders(relFolders ...string) RunOption {
117117
})
118118
}
119119

120+
// RootURIAsDefaultFolder configures the RootURI initialization
121+
// parameter to be set to the default workdir root. This is sent to the
122+
// LSP server as part of initialization.
123+
func RootURIAsDefaultFolder() RunOption {
124+
return optionSetter(func(opts *runConfig) {
125+
opts.editor.RootURIAsDefaultFolder = true
126+
})
127+
}
128+
120129
// The InitializeError RunOption establishes the expectation that creating a
121130
// new fake editor (happens as part of Run) will fail with the non-empty error
122131
// string err.

0 commit comments

Comments
 (0)