Skip to content

Commit 641094c

Browse files
committed
[dev.go2go] goLanguageServer.ts: add support for .go2 files with gopls
This change checks if the user has a version of go that supports go2go, and if so, translates URIs as they go between the extension and the language server. It is definitely excessive to stat files up to 2 times every time they are requested, but caching known files gets us into difficult territory with deletions, renames, etc. I'd rather avoid it and sacrifice a little speed. Change-Id: I8c1e2a1973e9715c16a772bffe46fe7d3cb4b341 Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/238244 Reviewed-by: Hyang-Ah Hana Kim <[email protected]>
1 parent 51b6e00 commit 641094c

File tree

2 files changed

+72
-22
lines changed

2 files changed

+72
-22
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@
9292
{
9393
"id": "go",
9494
"extensions": [
95-
".go"
95+
".go",
96+
"go2"
9697
],
9798
"aliases": [
9899
"Go"

src/goLanguageServer.ts

Lines changed: 70 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ import { GoWorkspaceSymbolProvider } from './goSymbol';
3939
import { getTool, Tool } from './goTools';
4040
import { GoTypeDefinitionProvider } from './goTypeDefinition';
4141
import { getFromGlobalState, updateGlobalState } from './stateUtils';
42-
import { getBinPath, getCurrentGoPath, getGoConfig } from './util';
42+
import { getBinPath, getCurrentGoPath, getGoConfig, getGoVersion } from './util';
4343

4444
interface LanguageServerConfig {
4545
serverName: string;
@@ -84,22 +84,43 @@ export async function startLanguageServerWithFallback(ctx: vscode.ExtensionConte
8484
if (activation && cfg.enabled && cfg.serverName === 'gopls') {
8585
const tool = getTool(cfg.serverName);
8686
if (tool) {
87-
const versionToUpdate = await shouldUpdateLanguageServer(tool, cfg.path, cfg.checkForUpdates);
88-
if (versionToUpdate) {
89-
promptForUpdatingTool(tool.name, versionToUpdate);
90-
} else if (goplsSurveyOn) {
91-
// Only prompt users to fill out the gopls survey if we are not
92-
// also prompting them to update (both would be too much).
93-
const timeout = 1000 * 60 * 60; // 1 hour
94-
setTimeout(async () => {
95-
const surveyCfg = await maybePromptForGoplsSurvey();
96-
flushSurveyConfig(surveyCfg);
97-
}, timeout);
87+
// Skip the update prompt - the user should have a special generics
88+
// version of gopls.
89+
if (false) {
90+
const versionToUpdate = await shouldUpdateLanguageServer(tool, cfg.path, cfg.checkForUpdates);
91+
if (versionToUpdate) {
92+
promptForUpdatingTool(tool.name, versionToUpdate);
93+
} else if (goplsSurveyOn) {
94+
// Only prompt users to fill out the gopls survey if we are not
95+
// also prompting them to update (both would be too much).
96+
const timeout = 1000 * 60 * 60; // 1 hour
97+
setTimeout(async () => {
98+
const surveyCfg = await maybePromptForGoplsSurvey();
99+
flushSurveyConfig(surveyCfg);
100+
}, timeout);
101+
}
102+
}
103+
}
104+
}
105+
106+
// Run `go tool go2go help` to check if the user is working with a version
107+
// of Go that supports the generics prototype. This will error either way,
108+
// but we can check the error message to see if the command was recognized.
109+
let usingGo2Go: boolean = false;
110+
const goRuntimePath = getBinPath('go');
111+
if (goRuntimePath) {
112+
const execFile = util.promisify(cp.execFile);
113+
try {
114+
await execFile(goRuntimePath, ['tool', 'go2go', 'help'], { env: cfg.env });
115+
} catch (err) {
116+
const errStr = `${err}`;
117+
if (errStr.indexOf('Usage: go2go <command> [arguments]') !== -1) {
118+
usingGo2Go = true;
98119
}
99120
}
100121
}
101122

102-
const started = await startLanguageServer(ctx, cfg);
123+
const started = await startLanguageServer(ctx, cfg, usingGo2Go);
103124

104125
// If the server has been disabled, or failed to start,
105126
// fall back to the default providers, while making sure not to
@@ -109,7 +130,8 @@ export async function startLanguageServerWithFallback(ctx: vscode.ExtensionConte
109130
}
110131
}
111132

112-
async function startLanguageServer(ctx: vscode.ExtensionContext, config: LanguageServerConfig): Promise<boolean> {
133+
async function startLanguageServer(
134+
ctx: vscode.ExtensionContext, config: LanguageServerConfig, usingGo2Go: boolean): Promise<boolean> {
113135
// If the client has already been started, make sure to clear existing
114136
// diagnostics and stop it.
115137
if (languageClient) {
@@ -128,7 +150,7 @@ async function startLanguageServer(ctx: vscode.ExtensionContext, config: Languag
128150
// Track the latest config used to start the language server,
129151
// and rebuild the language client.
130152
latestConfig = config;
131-
languageClient = await buildLanguageClient(config);
153+
languageClient = await buildLanguageClient(config, usingGo2Go);
132154
}
133155

134156
// If the user has not enabled the language server, return early.
@@ -158,7 +180,7 @@ async function startLanguageServer(ctx: vscode.ExtensionContext, config: Languag
158180
return true;
159181
}
160182

161-
async function buildLanguageClient(config: LanguageServerConfig): Promise<LanguageClient> {
183+
async function buildLanguageClient(config: LanguageServerConfig, usingGo2Go: boolean): Promise<LanguageClient> {
162184
// Reuse the same output channel for each instance of the server.
163185
if (config.enabled && !serverOutputChannel) {
164186
serverOutputChannel = vscode.window.createOutputChannel(config.serverName);
@@ -173,12 +195,39 @@ async function buildLanguageClient(config: LanguageServerConfig): Promise<Langua
173195
},
174196
{
175197
initializationOptions: {},
176-
documentSelector: ['go', 'go.mod', 'go.sum'],
198+
documentSelector: ['go', 'go2', 'go.mod', 'go.sum'],
177199
uriConverters: {
178200
// Apply file:/// scheme to all file paths.
179-
code2Protocol: (uri: vscode.Uri): string =>
180-
(uri.scheme ? uri : uri.with({ scheme: 'file' })).toString(),
181-
protocol2Code: (uri: string) => vscode.Uri.parse(uri)
201+
code2Protocol: (uri: vscode.Uri): string => {
202+
if (usingGo2Go) {
203+
uri = (uri.scheme ? uri : uri.with({ scheme: 'file' }));
204+
// If the file has a *.go2 suffix, try stripping it.
205+
const uriPath = uri.path.replace('.go2', '.go');
206+
uri = uri.with({ path: uriPath });
207+
return uri.toString();
208+
}
209+
return (uri.scheme ? uri : uri.with({ scheme: 'file' })).toString();
210+
},
211+
protocol2Code: (uri: string) => {
212+
if (usingGo2Go) {
213+
const parsed = vscode.Uri.parse(uri);
214+
try {
215+
fs.statSync(parsed.fsPath);
216+
return parsed;
217+
} catch (err) {
218+
// Try adding a 'go2' suffix to a Go file and see if it exists.
219+
const uriPath = parsed.fsPath.replace('.go', '.go2');
220+
try {
221+
fs.statSync(uriPath);
222+
return parsed.with({ path: parsed.path.replace('.go', '.go2') });
223+
} catch (err) {
224+
// do nothing?
225+
}
226+
}
227+
return parsed;
228+
}
229+
return vscode.Uri.parse(uri);
230+
},
182231
},
183232
outputChannel: serverOutputChannel,
184233
revealOutputChannelOn: RevealOutputChannelOn.Never,
@@ -494,7 +543,7 @@ export async function shouldUpdateLanguageServer(
494543

495544
// If the user's version does not contain a timestamp,
496545
// default to a semver comparison of the two versions.
497-
const usersVersionSemver = semver.coerce(usersVersion, {includePrerelease: true, loose: true});
546+
const usersVersionSemver = semver.coerce(usersVersion, { includePrerelease: true, loose: true });
498547
return semver.lt(usersVersionSemver, latestVersion) ? latestVersion : null;
499548
}
500549

0 commit comments

Comments
 (0)